iOS多线程实现——GCD的基本使用

今天和大家一起来讨论一下GCD的基本使用,有疏忽的地方,还望各位不吝赐教。


一、多线程简介

1、多线程相关的概念

进程:

1、进程是指在系统中正在运行的一个应用程序;
2、每个进程是独立的,每个进程运行在专用且受保护的内存空间中。

线程:

1、一个进程执行任务,必须依赖线程(一个进程至少有一条线程);
2、进程的所有任务都在线程中执行。

线程的串行:

1、一个线程中任务的执行是串行的
2、如果要在一个线程中执行多个任务,只能一个一个按顺序执行,即同一时间一个线程只能执行一个任务;

线程和进程的比较:

1、线程是CPU调用(执行任务)的最小单位。
2、进程是CPU分配资源的最小单位。
3、一个程序可以对应多个进程,一个进程中可以有多个线程,但至少有一个。
4、同一个进程中的线程共享进程的资源。

多线程:

一个进程中可以开启多条线程的,每一条线程可以并行(同时)执行不同的任务。其实在同一时间,CPU只能处理一条线程,多线程的并发(同时)执行,其实是一种假象,只不过是快速的在多条线程之间进行调度,也就是切换罢了。但是一般不建议开很多条线程,因为开线程越多,相应的开销就越大,对于CPU来说负担就越重。

2、多线程的优缺点

优点:
1、能适当的提高程序的执行效率;
2、能适当的提高资源的利用率(包括CPU和内存)。
缺点:
1、创建线程是有开销的,iOS 9下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB,主线程1MB,也可以使用-setStackSize:进行设置,但是必须是4KB的倍数,最小16KB),创建线程大约需要90毫秒的时间进行创建。
2、如果开启大量的线程,会降低程序的性能。
3、线程越多,CPU在线程上的开销就越大。
4、程序设计更加复杂,包括在线程之间的通信和多线程之间的数据共享。

3、多线程在iOS中的应用

主线程

一个iOS程序运行后,默认会开启一条线程,称为主线程或者UI线程。

主线程的主要作用

1、显示\刷新UI界面;
2、处理UI事件(比如点击事件,滚动事件和拖拽事件等);
3、注意不要将耗时操作放到主线程汇总;
4、耗时操作会卡住主线程,严重影响UI的流畅度,造成不好的用户体验;
5、在开发过程中,注意将耗时操作放到子线程中进行执行。

二、多线程安全

当多个线程同时访问一块资源的时候,会发生数据错乱和数据安全的问题。
解决方案:互斥锁
当线程A访问资源的时候加锁,保证其他线程无法在线程A访问期间进行对本资源的操作,在线程A操作完成后,会打开添加的互斥锁,之后其他线程可以按照同样的方式进行访问资源。

    格式:
    @synchronized(锁对象) {需要锁定的代码}
    使用方式:
    @synchronized(self) {}

注意点:
1、锁必须是全局唯一的,所以一般使用控制器作为标识。
2、注意加锁的前提条件,在多线程访问同一块资源的时候再添加。
3、加锁是需要耗费性能的。
4、加锁的结果:线程同步(多条线程在同一条线上执行,按照顺序进行执行)
关于原子属性和非原子属性的解释:
*atomic:
原子属性,会为setter方法加锁,默认为atomic。
因为会为setter方法加锁,所以在使用的时候是线程安全的,但是会消耗大量的资源。
*nonatomic:
非原子属性,不会为setter方法加锁。
虽然nonatomic是不安全的,但是出现多个线程同时访问一块资源的情况很少,为了保证效率,所以一般使用nonatomic。
建议:
尽量把这些争夺资源的任务交给服务器端处理,减小移动端的压力。

三、iOS多线程实现方案

技术方案 简介 语言 线程生命周期 使用频率
pthread 一套通用的多线程API
可移植,适用于多个平台
使用难度大
C 程序员管理 几乎不用
NSThread 使用更加面向对象
简单易用,可以直接操作线程对象
OC 程序员管理 偶尔使用
GCD 为了替代NSThread等的线程技术
充分利用设备的多核
C 自动管理 经常使用
NSOperation 基于GCD
相比GCD多了一些更简单实用的功能
更加面向对象
OC 自动管理 经常使用

四、GCD多线程实现方案

1、GCD简介

全称是Grand Central Dispatch,是一个纯C语言的库,是苹果公司为多核的并行运算提出的解决方案。

2、GCD优势

1、GCD会自动利用更多的CPU内核(比如双核和四核)
2、GCD会自动管理线程的生命周期(创建线程,调度线程和销毁线程)
3、程序员不需要手动编写管理线程的任何代码,只需要告诉GCD想要执行什么任务

3、任务和队列

任务:执行的操作统称为任务
队列:用来存放任务,并且调度任务在那些线程中执行

4、使用步骤

1、定制任务
确定想做的任务
2、将任务添加到队列中
GCD会自动将队列中的任务取出来,放到对应的线程中执行
任务的取出遵循队列的FIFO的原则:先进先出,后进后出

5、执行任务的方式

GCD中有两个用来执行任务的常用函数

  • 同步函数
    同步:只能在当前线程中执行任务,不具备开启新线程的能力
  • 异步函数
    异步:可以在新的线程中执行任务,具备开启新线程的能力

6、队列的类型

GCD中队列分为两大类型

  • 并发队列(Concurrent Dispatch Queue)
    可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
  • 串行队列 (Serial Dispatch Queue)
    可以让任务一个接着一个的执行(一个执行完之后,再执行下一个任务)

7、术语的相关解释

  • 同步和异步主要影响的是能不能开启新的线程 这里指的是函数
    同步:只能在当前线程中执行任务,不具备开启新线程的能力
    异步:可以在新的线程中执行任务,具备开启新线程的能力
  • 并发和串行主要影响:任务的执行方式 这里指的是队列
    并发:可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
    串行:可以让任务一个接着一个的执行(一个执行完之后,再执行下一个任务)

五、pthread简单使用

因为pthread用的不多,所以简单提一下,关于其创建线程的使用如下:

   // 1、创建线程对象
    pthread_t thread;
    
    // 2、创建线程
    /*
     * 第一个参数:线程对象,传递地址
     * 第二个参数:线程的属性 NULL
     * 第三个参数:指向函数的指针
     * 第四个参数:函数需要接受的参数
     */
    pthread_create(&thread, NULL, task, NULL);
    // 3、task实现
    void *task(void *param){

        NSLog(@"%@",[NSThread currentThread]);
        return NULL;
    }

六、GCD简单使用

创建并发队列的方式

1、create创建并发队列(主动创建)

    /*
     * 第一个参数:c语言的字符串,标签
     * 第二个参数:队列的类型
     * DISPATCH_QUEUE_CONCURRENT 并发
     * DISPATCH_QUEUE_SERIAL 串行
     */
    
    dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd",DISPATCH_QUEUE_CONCURRENT);
    

2、获取全局并发队列(系统中本身存在,拿过来用就行了)

  /* 和DISPATCH_QUEUE_CONCURRENT创建的并发队列 是等价的
   * 第一个参数:优先级 四种 一般用默认的即可
        #define DISPATCH_QUEUE_PRIORITY_HIGH 2
        #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
        #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
        #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 最低的优先级
   * 第二个参数:系统说明是给未来使用的,使用的时候传参为0即可。
   */
  
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  

创建串行队列的方式

1、create创建串行队列(主动创建)

    /*
     * 第一个参数:c语言的字符串,标签
     * 第二个参数:队列的类型
     * DISPATCH_QUEUE_CONCURRENT 并发
     * DISPATCH_QUEUE_SERIAL 串行 或者传递NULL 
     */
    
    dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd",DISPATCH_QUEUE_SERIAL);
    

2、使用主队列(跟主线程相关联的队列)

    /*
   * 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务都会放在主线程中进行执行。
   * 第一个参数:c语言的字符串,标签
   * 第二个参数:队列的类型
   * DISPATCH_QUEUE_CONCURRENT 并发
   * DISPATCH_QUEUE_SERIAL 串行 或者传递NULL 
   */
  

函数和队列的组合情况

1、异步函数 + 并发队列: 会开启多条线程,队列中的任务是异步(并发)执行

    // 1.创建队列
    /*
     * 第一个参数:c语言的字符串,标签
     * 第二个参数:队列的类型
     * DISPATCH_QUEUE_CONCURRENT 并发
     * DISPATCH_QUEUE_SERIAL 串行
     */
    
    dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd", DISPATCH_QUEUE_CONCURRENT);
    
    // 2、封装任务 > 将任务添加到队列中
    /* 
     * 第一个参数:要执行的队列
     * 第二个参数:要执行的任务
     * 注意:
        并不是有几个任务就开几条线程,具体开几条线程由系统进行衡量!
     */
    
    dispatch_async(queue, ^{
        NSLog(@"1---------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2---------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3---------%@",[NSThread currentThread]);
    });
    // 打印结果:
        3---------<NSThread: 0x60800007e880>{number = 5, name = (null)}
        1---------<NSThread: 0x600000078700>{number = 3, name = (null)}
        2---------<NSThread: 0x60800007e2c0>{number = 4, name = (null)}

2、异步函数 + 串行队列: 会开启一条线程,队列中的任务是同步(串行)执行的

    // 1.创建队列
    /*
     * 第一个参数:c语言的字符串,标签
     * 第二个参数:队列的类型
     * DISPATCH_QUEUE_CONCURRENT 并发
     * DISPATCH_QUEUE_SERIAL 串行
     */
    
    dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd", DISPATCH_QUEUE_SERIAL);
    
    // 2、封装任务 > 将任务添加到队列中
    /*
     * 第一个参数:要执行的队列
     * 第二个参数:要执行的任务
     */
    
    dispatch_async(queue, ^{
        NSLog(@"1---------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2---------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3---------%@",[NSThread currentThread]);
    });
    // 打印结果:
        1---------<NSThread: 0x60000007d180>{number = 3, name = (null)}
        2---------<NSThread: 0x60000007d180>{number = 3, name = (null)}
        3---------<NSThread: 0x60000007d180>{number = 3, name = (null)}

3、同步函数 + 并发队列:不会开线程,任务是串行执行的

    // 1.创建队列
    /*
     * 第一个参数:c语言的字符串,标签
     * 第二个参数:队列的类型
     * DISPATCH_QUEUE_CONCURRENT 并发
     * DISPATCH_QUEUE_SERIAL 串行
     */
    
    dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd", DISPATCH_QUEUE_CONCURRENT);
   
    // 2、封装任务 > 将任务添加到队列中
    /*
     * 第一个参数:要执行的队列
     * 第二个参数:要执行的任务
     */
    
    dispatch_sync(queue, ^{
        NSLog(@"1---------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2---------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3---------%@",[NSThread currentThread]);
    });
    // 打印结果:
        1---------<NSThread: 0x60000007e800>{number = 1, name = main}
        2---------<NSThread: 0x60000007e800>{number = 1, name = main}
        3---------<NSThread: 0x60000007e800>{number = 1, name = main}

4、同步函数 + 串行队列:不会开线程,任务是串行执行的(效果和3同)

    // 1.创建队列
    /*
     * 第一个参数:c语言的字符串,标签
     * 第二个参数:队列的类型
     * DISPATCH_QUEUE_CONCURRENT 并发
     * DISPATCH_QUEUE_SERIAL 串行
     */
    
    dispatch_queue_t queue = dispatch_queue_create("com.jianshu.gcd",DISPATCH_QUEUE_SERIAL);
    
    // 2、封装任务 > 将任务添加到队列中
    /*
     * 第一个参数:要执行的队列
     * 第二个参数:要执行的任务
     */
    
    dispatch_sync(queue, ^{
        NSLog(@"1---------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2---------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3---------%@",[NSThread currentThread]);
    });
    // 打印结果:
        1---------<NSThread: 0x60000007e800>{number = 1, name = main}
        2---------<NSThread: 0x60000007e800>{number = 1, name = main}
        3---------<NSThread: 0x60000007e800>{number = 1, name = main}

5、异步函数 + 主队列:不会开线程,任务是串行执行的

    // 1.获得主队列

    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2、封装任务 > 将任务添加到队列中
    /*
     * 第一个参数:要执行的队列
     * 第二个参数:要执行的任务
     */
    
    dispatch_async(queue, ^{
        NSLog(@"1---------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2---------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3---------%@",[NSThread currentThread]);
    });
    // 打印结果:
        1---------<NSThread: 0x60000007e800>{number = 1, name = main}
        2---------<NSThread: 0x60000007e800>{number = 1, name = main}
        3---------<NSThread: 0x60000007e800>{number = 1, name = main}

6、同步函数(立刻马上执行) + 主队列:死锁

    // 1.获得主队列

    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2、封装任务 > 将任务添加到队列中
    /*
     * 第一个参数:要执行的队列
     * 第二个参数:要执行的任务
     */
    
    dispatch_sync(queue, ^{
        NSLog(@"1---------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2---------%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3---------%@",[NSThread currentThread]);
    });
    
    // 说明:
        主队列的特点:如果主队列发现当前主线程正在执行任务,会等到主线程执行完当前任务后,再调度主队列中的任务,如果在子线程中使用以下方法就可以避免死锁
        同步函数:立刻马上执行,我不执行完,后面的谁都别想执行
        异步函数:如果我不执行完,后面的也可以执行

各种队列执行总结

并发队列 手动创建串行队列 主队列
同步 没有开启新线程
串行执行任务
没有开启新线程
串行执行任务
没有开启新线程
串行执行任务
异步 开启新线程
并发执行任务
开启新线程
串行执行任务
没有开启新线程
串行执行任务

七、GCD线程之间的通信

// 1、获取全局并发队列

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

// 2、封装任务 > 将任务添加到队列中

    dispatch_async(queue, ^{
        // 在这里执行子线程中相关耗时的操作
        /*
             例如网络请求或者下载任务
          */
        dispatch_async(dispatch_get_main_queue(), ^{
            
            // 在这里进行和主线程的通信,主要是刷新UI的相关操作,这里用同步函数也是可以的,并不会造成死锁。
            
        });
        
    });

八、GCD常用函数

1、延迟执行

    1)延迟执行的第一种方法
    [self performSelector:@selector(task) withObject:nil afterDelay:2.0];
    
    2)延迟执行的第二种方法
     [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];

    3)延迟执行的第二种方法 
    /* 主要可以控制延迟执行的操作在哪个队列中执行 dispatch_get_main_queue() 修改此参数即可
     * 第一个参数:DISPATCH_TIME_NOW 从现在开始计算时间
     * 第二个参数:延迟的时间 GCD的时间单位 纳秒
     * 第三个参数:dispatch_get_main_queue 主队列
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSLog(@"delay----%@",[NSThread currentThread]);
        
    });

2、一次性执行
使用场景:单例模式(在以后的分享中会提到。。。。。。)

    // static 静态全局变量,作用域是整个应用程序的生命周期
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"once-----");
    });
     注意:
             一次性代码不能放在懒加载中 会出问题
             因为此代码只执行一次,在第一次创建的时候会把对象返回,但是在第二次创建对象的时候,不会再执行一次性代码,所以对象返回值为空。

3、GCD的栅栏函数
控制并发队列中异步执行任务的执行顺序。

    // 获得全局并发队列
    // 栅栏函数不能使用全局并发队列
    //    dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 
    dispatch_queue_t queue = dispatch_queue_create("en", DISPATCH_QUEUE_CONCURRENT);
    // 异步函数
    dispatch_async(queue, ^{
        NSLog(@"1--------%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2--------%@",[NSThread currentThread]);
    });
    // 栅栏函数 先执行1、2,关于1、2的先后是不能确定的,再执行3。
    dispatch_barrier_async(queue, ^{
        NSLog(@"+++++++++++++++++++");
    });
    dispatch_async(queue, ^{
        NSLog(@"3--------%@",[NSThread currentThread]);
    });
      // 打印结果:
        1--------<NSThread: 0x6000000754c0>{number = 3, name = (null)}
        2--------<NSThread: 0x60000006a340>{number = 4, name = (null)}
        +++++++++++++++++++
        3--------<NSThread: 0x60000006a340>{number = 4, name = (null)}

4、GCD快速迭代
迭代其实就是遍历,说白了就是一个循环。

    // 1)快速迭代的第一种方法
        for循环,但是for循环是同步执行的。

    // 2)快速迭代的第二种方法
    /* 主线程和子线程一起完成任务 任务的执行是并发的
     * 第一个参数:迭代的次数
     * 第二个参数:并发队列 只能传并发队列,如果传主队列,死锁,如果传手动创建的串行队列跟for循环效果一样
     * 第三个参数:索引
     */
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        
        NSLog(@"%@-----%zu",[NSThread currentThread],index);
        
    });

5、GCD定时器

    /** 定时器 */
    @property (nonatomic, strong) dispatch_source_t timer;
    // 创建timer
    /* GCD中的定时器是绝对精准的,不会受到RunLoop的影响!
     * 第一个参数:类型 DISPATCH_SOURCE_TYPE_TIMER
     * 第二个参数:描述信息
     * 第三个参数:更详细的描述信息
     * 第四个参数:队列 决定GCD的timer在那个线程中执行
     */
    // 以局部变量的形式进行定义,timer不会执行的,因为在执行之前已经被释放了
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    /*
     * 第一个参数:定时器对象
     * 第二个参数:DISPATCH_TIME_NOW 起始时间
     * 第三个参数:间隔时间
     * 第四个参数:精准度 允许误差,此处传0即可
     */
    dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
    //  设置定时器的任务
    dispatch_source_set_event_handler(self.timer, ^{
        
        NSLog(@"执行任务");
        
    });
    // 开启定时器
    dispatch_resume(self.timer);

九、GCD队列组的使用

跟栅栏函数相类似,也是用来控制并发队列中异步执行任务的执行顺序的。

    // 1、获得全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 2、创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 3、异步函数
    /* 这个函数做了什么?
      1、封装任务 
      2、将任务添加到队列中
      3、会监听任务执行情况,通知group
    */
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"1---------%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"2---------%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSLog(@"3---------%@",[NSThread currentThread]);
    });
    // 4、拦截通知,当所有的队列组中的任务执行完毕以后会执行这个方法
    dispatch_group_notify(group, queue, ^{
        NSLog(@"end--------end");
    });
    // 打印结果:
        2---------<NSThread: 0x60800007a8c0>{number = 7, name = (null)}
        1---------<NSThread: 0x60800007a800>{number = 6, name = (null)}
        3---------<NSThread: 0x608000078980>{number = 8, name = (null)}
        end--------end

另外一种GCD队列组的实现方式(以前使用的)

    // 获得全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 此方法之后执行的执行的异步任务会被纳入到队列组的监听范围内
    // 配对使用
    dispatch_group_enter(group);
    
    dispatch_group_async(group, queue, ^{
       NSLog(@"1---------%@",[NSThread currentThread]);
        
        // 离开群组
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"2---------%@",[NSThread currentThread]);
        
        // 离开群组
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"3---------%@",[NSThread currentThread]);
        
        // 离开群组
        dispatch_group_leave(group);
    });
    
    // 当所有的队列组中的任务执行完毕以后会执行这个方法
    // 内部本身是异步的,不会阻塞
    dispatch_group_notify(group, queue, ^{
        
        NSLog(@"end--------end");
    });

    // 其他的监听方法 DISPATCH_TIME_FOREVER 死等 等到所有任务都执行完毕才执行这个方法
    // 这里是阻塞的,如果下面方法没有执行,以后的方法不会执行的。
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

十、GCD异步函数的补充

GCD异步函数有两种实现方式,具体的异同比较如下:

 1、block 实现
 // dispatch_async(<dispatch_queue_t  _Nonnull queue>, <^(void)block>)
 2、方法调用的方式实现
    /*
     * 第一个参数:队列
     * 第二个参数:参数
     * 第三个参数:要调用的函数名称
     */
    dispatch_async_f(dispatch_get_global_queue(0, 0), nil ,task);
    dispatch_async_f(dispatch_get_global_queue(0, 0), nil ,task);
    dispatch_async_f(dispatch_get_global_queue(0, 0), nil ,task);
//  调用函数的实现
void task(void *params){
    NSLog(@"%s--------%@",__func__,[NSThread currentThread]);
}

写在最后的话:关于GCD的知识今天就分享到这里,关于iOS多线程实现方面的问题欢迎大家和我交流,共同进步,谢谢各位。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354