ios 多线程-GCD

多线程原理这篇文章中,我们讲述了关于多线程的一些基础点和需要注意的事项。就目前开发情况来看我们大多数情况都是用GCD就能解决了,今天我们就来探讨一下GCD的用法。

GCD

GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法。三种开发中GCD抽象层次最高,当然是用起来也最简单,只是它基于C语言开发,并不像NSOperation是面向对象的开发,而是完全面向过程的。对于熟悉C#异步调用的朋友对于GCD学习起来应该很快,因为它与C#中的异步调用基本是一样的。这种机制相比较于前面两种多线程开发方式最显著的优点就是它对于多核运算更加有效。

名词解释

任务:执行什么操作
队列:用来存放任务,将需要执行的任务添加到队列中,队列会遵循FIFO原则(先进先出、后进后出),将队列中的任务取出,放到对应的线程中执行

同步:不创建新的线程,只在当前线程中执行任务
异步:创建多条线程执行任务

串行:同一时间每次只能执行一个任务,当前任务未完成下一个任务只能在队列中等候
并发:同一时间可以执行多个任务

死锁:两个或多个任务互相等待形成死循环阻塞了线程,甚至导致应用无响应

关系

串行 并行 主队列
同步 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁
异步 开启新线程,串行执行任务 开启新线程,并发执行任务 没有开启新线程,串行执行任务

1、队列

队列的特点:遵循先进先出FIFO(First In First Out)的原则,排在前面的任务最先执行。
创建队列使用dispatch_queue_create,有两个参数,第一个参数是队列名称,第二个参数是队列类型,通常创建串行队列类型传NULL,我们也可以使用dispatch_queue_attr_t定义好的常量创建同步/并发队列

/**
dispatch_queue_attr_t
DISPATCH_QUEUE_SERIAL: 同步队列
DISPATCH_QUEUE_CONCURRENT:并发队列
*/
dispatch_queue_t queue = dispatch_queue_create ( const char *label, dispatch_queue_attr_t attr );
1、串行队列
  • 任务按照顺序被调度,前一个任务不执行完毕,队列不会调度,也就是串行执行队列每次只能调度一个任务。
dispatch_queue_t queue = dispatch_queue_create("com.yx.customQueue", NULL);
  • 获取串行主队列,应用程序在创建时系统会自带串行主队列,主队列的任务都会在主线程中执行,一般用于更新UI
diapatch_queue_t queue = dispatch_get_main_queue();
2、并发队列

并发队列同样是使用dispatch_queue_create()方法创建,只是最后一个参数指定为DISPATCH_QUEUE_CONCURRENT进行创建,但是在实际开发中我们通常不会重新创建一个并发队列而是使用dispatch_get_global_queue()方法取得一个全局的并发队列(当然如果有多个并发队列可以使用前者创建)。

/**
队列优先级
DISPATCH_QUEUE_PRIORITY_HIGH 2               // 高
DISPATCH_QUEUE_PRIORITY_DEFAULT 0            // 默认(中)
DISPATCH_QUEUE_PRIORITY_LOW (-2)             // 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
// dispatch_queue_t queue = dispatch_queue_create("com.yx.customQueue", DISPATCH_QUEUE_CONCURRENT);
举例
1、同步/异步串行队列
// ✅ 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("yx.com", NULL);
    NSLog(@"begin");
// ✅ 异步串行队列
    dispatch_async(queue, ^{
        NSLog(@"异步线程1 - 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"队列外1 - 当前线程:%@",[NSThread currentThread]);

    dispatch_async(queue, ^{
        NSLog(@"异步线程2 - 当前线程:%@",[NSThread currentThread]);
    });
    
    NSLog(@"队列外2 - 当前线程:%@",[NSThread currentThread]);
// ✅ 同步串行队列
    dispatch_sync(queue, ^{
        NSLog(@"同步线程1 - 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"队列外3 - 当前线程:%@",[NSThread currentThread]);
// ✅ 异步串行队列
    dispatch_async(queue, ^{
        NSLog(@"异步线程3 - 当前线程:%@",[NSThread currentThread]);
    });

输出:

2020-02-22 17:06:06.725804+0800 004---线程通讯[17706:205885] begin
2020-02-22 17:06:06.726097+0800 004---线程通讯[17706:205885] 队列外1 - 当前线程:<NSThread: 0x6000034f6900>{number = 1, name = main}
2020-02-22 17:06:06.726097+0800 004---线程通讯[17706:206188] 异步线程1 - 当前线程:<NSThread: 0x600003492c80>{number = 3, name = (null)}
2020-02-22 17:06:06.726239+0800 004---线程通讯[17706:206188] 异步线程2 - 当前线程:<NSThread: 0x600003492c80>{number = 3, name = (null)}
2020-02-22 17:06:06.726242+0800 004---线程通讯[17706:205885] 队列外2 - 当前线程:<NSThread: 0x6000034f6900>{number = 1, name = main}
2020-02-22 17:06:06.726358+0800 004---线程通讯[17706:205885] 同步线程1 - 当前线程:<NSThread: 0x6000034f6900>{number = 1, name = main}
2020-02-22 17:06:06.726471+0800 004---线程通讯[17706:205885] 队列外3 - 当前线程:<NSThread: 0x6000034f6900>{number = 1, name = main}
2020-02-22 17:06:06.726621+0800 004---线程通讯[17706:206188] 异步线程3 - 当前线程:<NSThread: 0x600003492c80>{number = 3, name = (null)}
小结
  • 同步串行队列使用主线程顺序执行任务,如果当前任务没有完成,不会继续执行同步块以外的其他代码
  • 异步串行队列会创建一条新的线程,在当前线程中顺序执行任务,队列外的代码会在主线程中顺序执行,不受队列中任务的干扰
2、同步/异步并发队列
 // ✅ 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 // ✅ 手动创建并发队列
 // dispatch_queue_t queue = dispatch_queue_create("yx.com", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"Begin");
 // ✅ 异步并发队列
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"异步任务1 - 当前线程 - %@",[NSThread currentThread]);
    });
    
    NSLog(@"队列1 - 当前线程 - %@",[NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"异步任务2 - 当前线程 - %@",[NSThread currentThread]);
    });
    
     NSLog(@"队列2 - 当前线程 - %@",[NSThread currentThread]);
    
 // ✅ 同步并发队列
    dispatch_sync(queue, ^{
        NSLog(@"同步任务1 - 当前线程 - %@",[NSThread currentThread]);
    });
    
     NSLog(@"队列3 - 当前线程 - %@",[NSThread currentThread]);
    
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"异步任务3 - 当前线程 - %@",[NSThread currentThread]);
    });
    
    NSLog(@"End");

输出:

2020-02-25 10:50:40.431234+0800 003---GCD应用[9132:83160] Begin
2020-02-25 10:50:40.431455+0800 003---GCD应用[9132:83160] 队列1 - 当前线程 - <NSThread: 0x6000004e9000>{number = 1, name = main}
2020-02-25 10:50:40.431632+0800 003---GCD应用[9132:83160] 队列2 - 当前线程 - <NSThread: 0x6000004e9000>{number = 1, name = main}
2020-02-25 10:50:40.431762+0800 003---GCD应用[9132:83160] 同步任务1 - 当前线程 - <NSThread: 0x6000004e9000>{number = 1, name = main}
2020-02-25 10:50:40.431869+0800 003---GCD应用[9132:83160] 队列3 - 当前线程 - <NSThread: 0x6000004e9000>{number = 1, name = main}
2020-02-25 10:50:40.431980+0800 003---GCD应用[9132:83160] End
2020-02-25 10:50:43.431661+0800 003---GCD应用[9132:83442] 异步任务1 - 当前线程 - <NSThread: 0x600000468200>{number = 3, name = (null)}
2020-02-25 10:50:43.432114+0800 003---GCD应用[9132:83449] 异步任务2 - 当前线程 - <NSThread: 0x600000468580>{number = 4, name = (null)}
2020-02-25 10:50:43.432469+0800 003---GCD应用[9132:84274] 异步任务3 - 当前线程 - <NSThread: 0x600000468600>{number = 5, name = (null)}
小结:
  • 同步并发队列不会创建新线程,依然在主线程中,与同步串行队列相同。
  • 异步并发队列会根据任务量进行同样数量的线程创建,在任务执行过程中,顺序不定。

2、线程间的通讯

在ios开发过程中,我们一般在主线程里面进行UI的刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而且我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

// ✅ 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // ✅ 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    // ✅ 创建异步任务
    dispatch_async(queue, ^{
        NSLog(@"执行任务 - 当前线程:%@",[NSThread currentThread]);
        sleep(3);
                
        dispatch_async(mainQueue, ^{
            NSLog(@"回来了 - 当前线程:%@",[NSThread currentThread]);
        });
        
    });

输出:

2020-02-25 15:15:13.007441+0800 003---GCD应用[25369:271831] 执行任务 - 当前线程:<NSThread: 0x600001dbfc00>{number = 3, name = (null)}
2020-02-25 15:15:16.012709+0800 003---GCD应用[25369:271762] 回来了 - 当前线程:<NSThread: 0x600001dc6900>{number = 1, name = main}

3、栅栏方法

1、dispatch_barrier_async
  • 异步栅栏用于等待队列中前面任务执行完自己才执行,而它后面的任务必须等待它执行完成之后才能执行
  • 异步栅栏不影响主线程的任务执行,但是队列中的任务必须准守上面的规则。

    dispatch_queue_t queue = dispatch_queue_create("yx.com", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"Begin");
    
    NSLog(@"队列任务1 - 当前线程:%@",[NSThread currentThread]);
    // ✅ 创建异步栅栏
    dispatch_barrier_async(queue, ^{
        sleep(3);
        NSLog(@"栅栏任务1- 当前线程:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"异步任务1- 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"队列任务2 - 当前线程:%@",[NSThread currentThread]);
    
    dispatch_sync(queue, ^{
        
        NSLog(@"同步任务1- 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"End");

输出:

2020-02-25 14:57:14.000492+0800 003---GCD应用[24130:254960] Begin
2020-02-25 14:57:14.001127+0800 003---GCD应用[24130:254960] 队列任务1 - 当前线程:<NSThread: 0x6000027be1c0>{number = 1, name = main}
2020-02-25 14:57:14.001319+0800 003---GCD应用[24130:254960] 队列任务2 - 当前线程:<NSThread: 0x6000027be1c0>{number = 1, name = main}
2020-02-25 14:57:17.003433+0800 003---GCD应用[24130:255085] 栅栏任务1- 当前线程:<NSThread: 0x6000027d3a00>{number = 3, name = (null)}
2020-02-25 14:57:17.003697+0800 003---GCD应用[24130:254960] 同步任务1- 当前线程:<NSThread: 0x6000027be1c0>{number = 1, name = main}
2020-02-25 14:57:17.003747+0800 003---GCD应用[24130:255708] 异步任务1- 当前线程:<NSThread: 0x6000027e4540>{number = 4, name = (null)}
2020-02-25 14:57:17.003812+0800 003---GCD应用[24130:254960] End
2、dispatch_barrier_sync
  • 同步栅栏会阻塞主线程等待当前任务执行完毕才能按照原队列的规则执行。
   dispatch_queue_t queue = dispatch_queue_create("yx.com", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"Begin");
    // ✅ 创建同步栅栏
    dispatch_barrier_sync(queue, ^{
        sleep(3);
        NSLog(@"栅栏任务1- 当前线程:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"异步任务1- 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"队列任务1 - 当前线程:%@",[NSThread currentThread]);
    
    dispatch_sync(queue, ^{
        
        NSLog(@"同步任务1- 当前线程:%@",[NSThread currentThread]);
    });
    NSLog(@"End");

输出:

2020-02-25 14:50:12.043837+0800 003---GCD应用[23685:249681] Begin
2020-02-25 14:50:15.044208+0800 003---GCD应用[23685:249681] 栅栏任务1- 当前线程:<NSThread: 0x600000ff0100>{number = 1, name = main}
2020-02-25 14:50:15.044446+0800 003---GCD应用[23685:249681] 队列任务1 - 当前线程:<NSThread: 0x600000ff0100>{number = 1, name = main}
2020-02-25 14:50:15.044457+0800 003---GCD应用[23685:249758] 异步任务1- 当前线程:<NSThread: 0x600000f8cf00>{number = 3, name = (null)}
2020-02-25 14:50:15.044594+0800 003---GCD应用[23685:249681] 同步任务1- 当前线程:<NSThread: 0x600000ff0100>{number = 1, name = main}
2020-02-25 14:50:15.044688+0800 003---GCD应用[23685:249681] End

4、信号量

我们带着问题来进行探究:
假设我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,我们怎么做?这个时候我们这里就可以用信号量控制一下最大开辟线程数。

定义

信号量就是一种可用来控制访问资源的数量的标识,设定一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们制定的信号量数量来执行多个线程。

函数介绍

GCD信号量主要有3个函数:

  • dispatch_semaphore_create(M):创建一个值为M的信号量。信号量的初值,如果小于0则会返回NULL
  • dispatch_semaphore_wait(信号量,等待时间):如果该信号量的值大于0,则使其信号量的值-1,否则,阻塞线程直到该信号量的值大于0或者达到等到时间
  • dispatch_semaphore_signal(信号量):释放信号量,使得该信号量的值加1
举例:

限制线程最大并发数

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // ✅ 信号量 -- gcd控制并发数
    // ✅ 同步
    // ✅ 总结:由于设定的信号值为2,先执行2个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);

    // ✅ 任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务1");
        sleep(3);
        NSLog(@"任务1完成");
        dispatch_semaphore_signal(semaphore);
    });

    // ✅ 任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务2");
        sleep(1);
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(semaphore);
    });

    // ✅ 任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务3");
        sleep(1);
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(semaphore);
    });

输出:

2020-02-25 16:26:34.830331+0800 003---GCD应用[30462:334167] 执行任务1
2020-02-25 16:26:34.830355+0800 003---GCD应用[30462:334166] 执行任务3
2020-02-25 16:26:35.834183+0800 003---GCD应用[30462:334166] 任务3完成
2020-02-25 16:26:35.834384+0800 003---GCD应用[30462:334168] 执行任务2
2020-02-25 16:26:36.837841+0800 003---GCD应用[30462:334168] 任务2完成
2020-02-25 16:26:37.834082+0800 003---GCD应用[30462:334167] 任务1完成
小结:

由于设定的信号值为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2。

5、调度组

可以将一组block提交到调度组(dispatch_group)中,执行逐个串行回调。

函数介绍
  • dispatch_group_t group = dispatch_group_create(void):创建一个调度组。
  • dispatch_group_async: 将一个block(代码块)加入到dispatch_queue_t queue中并和dispatch_group_t group相关联。
  • dispatch_group_enter(group)dispatch_group_leave(group): 调用这个方法标志着一个代码块被加入了group,和dispatch_group_async功能类似;dispatch_group_enter()、dispatch_group_leave()必须成对出现
  • dispatch_group_notify: 当关联到dispatch_group_t上的dispatch_group_async任务执行完毕或者是关联在上面的dispatch_group_enter、dispatch_group_leave成对出现了。参数中的dispatch_block_t block会被提交到dispatch_queue_t queue中执行。
  • dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout):和dispatch_group_notify功能类似,多了一个timeout可以设置等待时间。当group任务执行完成之前,会阻塞当前线程,一直等待(不能放到主线程中)。当group上的任务执行完毕或者超过等待时间,才会进行结束等待。
示例:
1、dispatch_group_async
// ✅ 创建调度组
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // ✅ 创建组内任务
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务y1完成");
        sleep(3);
    });
    dispatch_group_async(group, queue, ^{

        NSLog(@"任务2完成");
    });
    
    // ✅ 设置等待
    dispatch_async(queue, ^{
     // ✅ DISPATCH_TIME_NOW 这里是超过设置的等待时间就向下执行。
     // ✅ DISPATCH_TIME_FOREVER 这里是等待组内任务执行完毕后继续执行,不管等待时间。
        dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 ));
        NSLog(@"结束");
    });
    
    // ✅ 创建组内任务完成
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
         NSLog(@"全部完成");
    });

输出:

2020-02-25 17:45:09.020782+0800 003---GCD应用[35685:404362] 任务2完成
2020-02-25 17:45:09.020784+0800 003---GCD应用[35685:404363] 结束
2020-02-25 17:45:12.025171+0800 003---GCD应用[35685:403742] 任务1完成
2020-02-25 17:45:12.025554+0800 003---GCD应用[35685:403650] 全部完成
2、dispatch_group_enter(group)dispatch_group_leave(group)
 // ✅ 创建调度组
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // ✅ 将异步任务添加进组
    dispatch_group_enter(group);
    // ✅ 创建异步任务
    dispatch_async(queue, ^{
        NSLog(@"任务1完成");
        // ✅ 异步任务完成出组
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"任务2完成");
        dispatch_group_leave(group);
    });
    
    // ✅ 设置等待
    dispatch_async(queue, ^{
        dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 ));
        NSLog(@"结束");
    });
    
    // ✅ 创建组内任务完成
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部完成");
    });

输出:

2020-02-25 17:49:30.070295+0800 003---GCD应用[36015:408141] 任务1完成
2020-02-25 17:49:30.070295+0800 003---GCD应用[36015:408138] 任务2完成
2020-02-25 17:49:30.070295+0800 003---GCD应用[36015:408146] 结束
2020-02-25 17:49:30.103424+0800 003---GCD应用[36015:408021] 全部完成

6、延迟调用

我们经常会遇到这样的需求:在指定时间(例如 3 秒)之后执行某个任务。可以用 GCDdispatch_after 方法来实现。

注意dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after方法是很有效的。

 // ✅ NSEC_PER_SEC : 1000000000ull 纳秒每秒 0.0000001 可以这么做参数
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
    // ✅ 串行队列来测试 延迟的方法是不是异步的!
    dispatch_queue_t queue = dispatch_queue_create("yx.com", DISPATCH_QUEUE_SERIAL);
    dispatch_after(time, queue, ^{
        NSLog(@"延迟打印");
    });
    NSLog(@"打印完了?");

输出:

2020-02-25 17:55:03.089045+0800 003---GCD应用[36371:412715] 打印完了?
2020-02-25 17:55:04.184112+0800 003---GCD应用[36371:413051] 延迟打印
小结:

由输出可以看出1 秒后异步追加任务代码到主队列,并开始执行。

以上是个人理解关于GCD的一些简单用法!闲来无事,做做记录!

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