GCD学习

多线程技术,首选的是NSOperation,在有些场合,考虑直接用GCD。c语言的风格,block的调用方式,更加灵活可控。
由于苹果推荐说用GCD,不过这是c风格了,跟Object-C这种面向对象的风格不是很符合。还是推荐NSOperation为主。当然,方便的时候,用用简单的经典的GCD也无妨。AFNetworking就是将NSOperationGCD结合起来用的,比较经典。
GCD中一些类型的定义在系统的如下路径
/usr/include/dispatch/

下面这两篇文章比较好地解释了同步异步,串行并行的概念,可以参考一下。虽然文章名字差不多,不过真的是不一样的,而且写得都还不错。
iOS多线程与GCD 你看我就够了
关于iOS多线程,你看我就够了

同步/异步(执行)

差异点在于是否阻塞当前的调用者线程,等待队列中的任务执行完毕。

同步执行

typedef void (^dispatch_block_t)(void);

dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
  • “阻塞”当前的调用者线程
  • “等待”队列中的任务执行完毕
  • 激活当前的调用者线程,“然后”继续往下执行其他代码
  • 如果队列中的任务长时间不返回,会出现界面“卡死”现象
  • 如果调用者线程和队列执行线程是“同一线程”,会出现“自己等自己”现象,结果是“崩溃或者死机”
    比如下面的代码如果在主线程执行,就会“崩溃或者死机”,原因就是出现了“主线程等主线程自己的情况”
NSLog(@"之前 - %@", [NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"sync - %@", [NSThread currentThread]);
});
NSLog(@"之后 - %@", [NSThread currentThread]);

异步执行

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

格式基本和同步的差不多,就一个单词的差别,但是表现完全两样。上面的例子用这个函数的话,就不会“崩溃或者死机”。其输出一般情况是这样的:

之前 - 
之后 - 
sync - 
  • 只是派发队列中的函数,让其去执行,不阻塞自己,不等待
  • 派发完后,直接执行下面的语句,碰到},自己就先返回
  • 队列中的函数如何执行,在哪执行,当前的调用者线程完全不关心
  • 异步调用可以使“当前的调用者”和“队列如何执行”毫无关系,充分隔离
  • 如果当前做的事和队列中要做的事没有先后依赖关系,那么推荐用异步调用的方式

串行/并行(队列)

dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);

第一个参数是队列的名字,是个字符串;
第二个参数决定队列的性质,串行或者并行;
虽然有ARC,但生成的Dispatch Queue必须由程序员主动释放。

dispatch_release(exampleSerialDispatchQueue);  // 释放
dispatch_retain(exampleSerialDispatchQueue);   // 持有
  • 两者的本质区别是:后面的任务是否等待前面的任务执行完毕

串行队列

  • 第二个参数是DISPATCH_QUEUE_SERIAL或者是"NULL"
  • 任务FIFO;因为只有一个管道,所以任务之间间有顺序依赖。就算是异步执行的,也要等前面的任务执行完毕,后面的任务才能执行
  • 队列的优先级较高,串行队列中的任务,有先后顺序依赖,后面的任务要等待
  • 如果任务间有顺序依赖,可以用串行队列,不需要考虑进程间同步的问题。“线程安全的”就是指串行队列。比如,可以把字典、数组操作放入一个串行队列,就解决“资源竞争”的问题了。
  • 只开辟一个线程执行,一个执行完了,再接着下一个,所以没有时序问题,是“线程安全的”
  • 主线程是一种串行队列,有时候主线程“卡死”,就是因为它在等待“耗时的任务”执行完毕
ispatch_queue_t dispatch_get_main_queue(void) {
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
}

并行队列

  • 第二个参数是DISPATCH_QUEUE_CONCURRENT
  • 任务也是FIFO。但是后面的任务不需要等待前面的任务执行结束,没有先后的依赖关系。
  • 后面的任务要后出队列,调度顺序上要落后一点。但是由于任务时间的关系。后面的任务完全有可能比前面的任务的先执行完毕
  • 并行队列不需要等待前面的任务执行完毕,效率上会高一点。如果任务间没有顺序上的依赖,推荐用并行队列
  • 如果是异步执行,可以开辟多个线程,任务间时序是不确定的。当然,如果是同步执行,任务间还是得等待,有先后依赖关系。
  • 系统提供的默认可用的并行队列
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

#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

第一个参数表示队列的优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT;第2个参数基本上是0

单例

+ (instancetype)sharedManager {
    static AFNetworkReachabilityManager *_sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        struct sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_len = sizeof(address);
        address.sin_family = AF_INET;

        _sharedManager = [self managerForAddress:&address];
    });

    return _sharedManager;
}
  • 这是AFNetworking中一个例子,单例基本就这个格式,非常方便
  • static可以是全局的,也可以是局部的。这里是局部的,其他文件访问不到,更好一点
  • typedef long dispatch_once_t; 本质是个long,这里有特殊含义,特殊用途

延时执行

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

typedef uint64_t dispatch_time_t;
#define DISPATCH_TIME_NOW (0ull)
#define DISPATCH_TIME_FOREVER (~0ull)

上面就是常用的函数和相应的配套函数和参数。第1个参数是时间,一般用第2个函数来获得,名字看上去都差不多。最小单位是纳秒,所以一般时间要用到下面几个常数定义。这个延时比NSTimer要精确一点,很多第3方库都在用,可以作为经典形式。
下面就是YYCache中的一个实际的例子

- (void)_trimRecursively {
    __weak typeof(self) _self = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        [self _trimInBackground];
        [self _trimRecursively];
    });
}

_autoTrimInterval就是要延迟执行的秒数

Group操作

有时候我们会有这种需求,在刚进去一个页面需要发送两个请求,并且更新页面操作必须在两个请求都结束(成功或失败)的时候才会执行。
第1种方式是用串行队列,两个网络请求依次进行,然后更新界面。这样做时序没有问题,不过当这两个网络请求之间没有相互依赖的话,效率损失比较大。
第2种方式是用NSOperationQueue,将最后的更新页面操作依赖前面2个网络操作就可以了,其他的事交给系统。这种是推荐的方式
第3种是用GCDdispatch_group函数族,可以获得更高的性能,更灵活地控制。这种方式不推荐,不过在AFNetworking中有比较大量的应用,所以也不反对使用。

dispatch_group_t dispatch_group_create(void);
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_leave(dispatch_group_t group);

dispatch_group_wait比较特殊,正常情况下不需要用到。比如,在这个例子中,两个网络请求之后就更新界面了。但是如果网络请求因为某种原因长时间不返回呢(这种情况还是蛮多的)?那就设一个超时时间吧。比如,最多等30s,如果还不返回,就显示网络不给力了。这个函数是同步的,傻等,结果看返回值,0表示成功。常见的超时等待处理方式。这种能力NSOperationQueue是没有的。

代码参考格式:

dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //在这里执行异步请求A
    并且在执行结束代码(成功或失败)中写上dispatch_group_leave(group);
});
dispatch_async(dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //在这里执行异步请求B
    并且在执行结束代码(成功或失败)中写上dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    //在这里执行异步请求B
});

NSOperationQueue复杂不了多少。

关于超时的参考格式:

#define kTimeOut   30ull 

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (kTimeOut * NESC_PER_SEC));
long result = dispatch_group_wait(group, time);
if(0 == result) { 
    // 在限定时间内返回;dispatch_group_notify执行,正常情况;啥也不用做,当然也可以log一下
} else { 
    // 超时了,任务还没有全部完成,dispatch_group_notify这时还没有执行
    // 如果这里显示超时,网络请求并不能取消,这个有点尴尬,dispatch_group_notify还是有可能执行的,只是观众不愿意等了
};

一些扫尾的工作

在文件object.h中有一些函数定义:

void dispatch_suspend(dispatch_object_t object);
void dispatch_resume(dispatch_object_t object);
void dispatch_cancel(void *object);
void dispatch_release(dispatch_object_t object);
  • 在上面超时的例子中,是不是可以把group直接dispatch_cancel掉?
  • 在离开页面的时候,是不是要把group dispatch_release
  • 有传说CGD的任务不能cancel,那是因为block是匿名函数,没法获得地址。如果把block用个变量保存起来,不是可以cancel了吗?或者直接cancel或者suspend block所在的queue,不是能取消了吗?

栅栏函数(这部分内容有点多,Option)

// 异步栅栏,比较常用
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

// 同步栅栏,会阻塞当前的调用者线程,
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);

栅栏函数可以看做是dispatch_group的升级版。就像一个分界点,将队列一分为二,前后两部分有顺序依赖。可以简单的认为:

  1. 执行栅栏函数之前加入队列的任务;等待,直到任务全部执行完毕
  2. 执行栅栏函数添加到队列中的任务;
  3. 等待栅栏函数添加的任务,直到任务全部执行完毕。
  4. 执行栅栏函数之后添加的任务

下面这篇文章写得很不错,值得好好看看。以前一直讨厌这个栅栏,这次算有点理解了,开始喜欢这个栅栏函数:
通过GCD中的dispatch_barrier_(a)sync加强对sync中所谓等待的理解

实际的例子

- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
    dispatch_barrier_async(_queue, ^{
        [_array insertObject:anObject atIndex:index];
    });
}
  • 这是weex框架中的一个例子
  • 在类WXThreadSafeMutableArray
  • 这里可以当做dispatch_group的简化使用,因为栅栏之后没内容,作用跟dispatch_group一样,不过使用简单很多,是一种很讨巧的方式
  • 鼓励这种用法,相比而言,dispatch_group实在有点复杂

自己写的一个例子

  • 任务用简单的sleep表示
  • 先加入的任务时间长(1秒),后加入的任务时间短(0.5秒),在同步的情况下是顺序的,在异步的情况下是倒序的,容易区分
  • 调用者线程就是主线程,好理解一点
case1: 异步栅栏在前,同步栅栏在后

这种情况,将有可能影响当前线程(主线程)的同步操作放在后面,对当前线程的影响最小,相对好理解一点。

代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    dispatch_queue_t currentQueue = dispatch_queue_create("currentQueue", DISPATCH_QUEUE_CONCURRENT);
    // 异步执行
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async 1");
    });
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_async 2");
    });
    
    // 栅栏异步执行
    dispatch_barrier_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_async");
    });
    NSLog(@"main thread after dispatch_barrier_async");
    
    // 栅栏同步执行
    dispatch_barrier_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_sync");
    });
    NSLog(@"main thread after dispatch_barrier_sync");
    
    // 同步执行
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_sync 3");
    });
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_sync 4");
    });
}

输出的log:

2017-03-09 17:09:25.573 Barrier[82252:5039848] main thread after dispatch_barrier_async
2017-03-09 17:09:26.141 Barrier[82252:5039953] dispatch_async 2
2017-03-09 17:09:26.639 Barrier[82252:5039954] dispatch_async 1
2017-03-09 17:09:26.639 Barrier[82252:5039954] dispatch_barrier_async
2017-03-09 17:09:26.640 Barrier[82252:5039848] dispatch_barrier_sync
2017-03-09 17:09:26.640 Barrier[82252:5039848] main thread after dispatch_barrier_sync
2017-03-09 17:09:27.715 Barrier[82252:5039848] dispatch_sync 3
2017-03-09 17:09:28.289 Barrier[82252:5039848] dispatch_sync 4

过程分析:

  1. main thread执行到dispatch_barrier_sync, 被阻塞,等待。这个时候,main thread after dispatch_barrier_async已经输出
  2. 任务1和2异步执行,逆序输出(2的任务时间短)
  3. 异步栅栏执行,输出dispatch_barrier_async
  4. 同步栅栏执行,输出dispatch_barrier_sync
  5. main thread被唤醒,继续执行,输出main thread after dispatch_barrier_sync
  6. main thread执行到dispatch_sync 3, 被阻塞,等待。
  7. 任务3,4同步执行,顺序输出(4的任务时间短也没用,要等3完成才能执行)
  8. main thread被唤醒,继续执行,碰到}返回,过程结束
case2: 同步栅栏在前,异步栅栏在后

这里主要看栅栏同步异步的影响

代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    dispatch_queue_t currentQueue = dispatch_queue_create("currentQueue", DISPATCH_QUEUE_CONCURRENT);
    // 异步执行
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async 1");
    });
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_async 2");
    });
    
    // 栅栏同步执行
    dispatch_barrier_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_sync");
    });
    NSLog(@"main thread after dispatch_barrier_sync");
    
    // 栅栏异步执行
    dispatch_barrier_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_async");
    });
    NSLog(@"main thread after dispatch_barrier_async");
    
    // 同步执行
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_sync 3");
    });
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_sync 4");
    });
}

输出的log:

2017-03-09 20:59:47.526 Barrier[91896:5120619] dispatch_async 2
2017-03-09 20:59:48.027 Barrier[91896:5120621] dispatch_async 1
2017-03-09 20:59:48.528 Barrier[91896:5120438] dispatch_barrier_sync
2017-03-09 20:59:48.528 Barrier[91896:5120438] main thread after dispatch_barrier_sync
2017-03-09 20:59:48.529 Barrier[91896:5120438] main thread after dispatch_barrier_async
2017-03-09 20:59:49.029 Barrier[91896:5120621] dispatch_barrier_async
2017-03-09 20:59:50.051 Barrier[91896:5120438] dispatch_sync 3
2017-03-09 20:59:50.626 Barrier[91896:5120438] dispatch_sync 4

过程分析:

  1. main thread执行到dispatch_barrier_sync, 被阻塞,等待。这个时候,啥也没有输出
  2. 任务1和2异步执行,逆序输出(2的任务时间短)
  3. 同步栅栏执行,输出dispatch_barrier_sync
  4. main thread执行到dispatch_sync 3, 被阻塞,等待。这段时间main thread做的事情有:(a)main thread after dispatch_barrier_sync输出;(b)异步栅栏被分配,但是main thread没有被阻塞;(c)main thread after dispatch_barrier_async输出;
  5. 异步栅栏执行,输出dispatch_barrier_async
  6. 任务3,4同步执行,顺序输出(4的任务时间短也没用,要等3完成才能执行)
  7. main thread被唤醒,继续执行,碰到}返回,过程结束
case3: 同步过程在前,异步过程在后

这个例子,调用者线程(这里是main thread)分配任务之后先于工作者线程返回,是大多数要用到的情况。

代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    dispatch_queue_t currentQueue = dispatch_queue_create("currentQueue", DISPATCH_QUEUE_CONCURRENT);
    
    // 同步执行
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_sync 3");
    });
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_sync 4");
    });
    
    // 栅栏同步执行
    dispatch_barrier_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_sync");
    });
    NSLog(@"main thread after dispatch_barrier_sync");
    
    // 栅栏异步执行
    dispatch_barrier_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_async");
    });
    NSLog(@"main thread after dispatch_barrier_async");
    
    // 异步执行
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async 1");
    });
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_async 2");
    });
}

输出的log:

2017-03-09 21:08:39.715 Barrier[92752:5127802] dispatch_sync 3
2017-03-09 21:08:40.285 Barrier[92752:5127802] dispatch_sync 4
2017-03-09 21:08:40.856 Barrier[92752:5127802] dispatch_barrier_sync
2017-03-09 21:08:40.856 Barrier[92752:5127802] main thread after dispatch_barrier_sync
2017-03-09 21:08:40.856 Barrier[92752:5127802] main thread after dispatch_barrier_async
2017-03-09 21:08:41.430 Barrier[92752:5127902] dispatch_barrier_async
2017-03-09 21:08:41.999 Barrier[92752:5127901] dispatch_async 2
2017-03-09 21:08:42.499 Barrier[92752:5127902] dispatch_async 1

过程分析:

  1. 任务3,4同步执行,顺序输出(4的任务时间短也没用,要等3完成才能执行)
  2. 同步栅栏执行,输出dispatch_barrier_sync
  3. 这段时间main thread一直被阻塞,直到同步栅栏执行完毕
  4. main thread被唤醒,继续执行,碰到}返回,main thread的过程结束。这段时间main thread做的事情有:(a)main thread after dispatch_barrier_sync输出;(b)异步栅栏被分配,但是main thread没有被阻塞;(c)main thread after dispatch_barrier_async输出;(d)任务1和2被分配,但是main thread没有被阻塞;
  5. 异步栅栏执行,输出dispatch_barrier_async
  6. 任务1和2异步执行,逆序输出(2的任务时间短)

小结

  • 同步过程会阻塞当前的调用者线程,等待block中的任务执行完毕。除非必要,同步过程尽量不要用
  • 栅栏函数可以作为dispatch_group一种简化用法,推荐使用。用异步栅栏就可以了。同步栅栏没有必要,增加理解的难度。
  • 串行队列可以用在“线程安全的字典”等场景,比较方便
  • 单例和延迟执行很经典,推荐使用
  • 能用dispatch_group和栅栏函数解决问题的地方就不要用mutex,信号量等进程间同步的技术,一不小心就死锁了
  • 栅栏比NSOperationQueue的依赖还好用,循环依赖也会带来死锁问题的。不过还是建议使用NSOperationQueue,上层API在内存管理等方面更让人省心和放心
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容