GCD

问题
  • 主要是runloop的原理以及核心源代码

  • gcd的原理及应用,source的原理为什么比timer更精准;

定义

GCD是异步执行任务的技术之一;一般将应用程序中记述的线程管理利用的代码在系统级中实现。
开发者只需要定义想要执行的任务并追加到适当的 Dispatch Queue中,GCD就能生成必要的线程并计划执行任务

熟悉了解一下NSThread
  • 特点:
    • 1)使用更加面向对象
    • 2)简单易用,可直接操作线程对象

- (void)test_NSThread
{
    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"1)是否是主线程 %d - %@",[currentThread isMainThread],currentThread);
    
    NSThread  *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run0) object:nil];
    [thread start]; //必须手动开启
    NSLog(@"2)是否是主线程 %d - %@",[thread isMainThread],thread);

    
    //创建线程后自动启动线程
    [NSThread detachNewThreadSelector:@selector(run1) toTarget:self withObject:nil];
    
    //隐式创建并启动线程
    [self performSelectorInBackground:@selector(run2) withObject:nil];


    //执行后台程序
    [self performSelectorInBackground:@selector(doWork) withObject:nil];
}

- (void)run0
{
    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"3)是否是主线程 %d - %@",[currentThread isMainThread],currentThread);
    
}

- (void)run1
{
    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"4)是否是主线程 %d - %@",[currentThread isMainThread],currentThread);
}

- (void)run2
{
    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"5)是否是主线程 %d - %@",[currentThread isMainThread],currentThread);
}

- (void)doWork
{
    sleep(5);
    //长时间处理结束,主线成使用其他处理
    [self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
    
    
}

- (void)doneWork
{
    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"6)是否是主线程 %d - %@",[currentThread isMainThread],currentThread);
}

输出

2019-01-15 13:30:02.439991+0800 GCD[1211:22656] 1)是否是主线程 1 - <NSThread: 0x6000034aa900>{number = 1, name = main}
2019-01-15 13:30:02.440236+0800 GCD[1211:22656] 2)是否是主线程 0 - <NSThread: 0x6000034cca80>{number = 3, name = main}
2019-01-15 13:30:02.441207+0800 GCD[1211:22695] 4)是否是主线程 0 - <NSThread: 0x6000034cc940>{number = 4, name = (null)}
2019-01-15 13:30:02.442023+0800 GCD[1211:22694] 3)是否是主线程 0 - <NSThread: 0x6000034cca80>{number = 3, name = (null)}
2019-01-15 13:30:02.444018+0800 GCD[1211:22696] 5)是否是主线程 0 - <NSThread: 0x6000034ccac0>{number = 5, name = (null)}
2019-01-15 13:30:07.470940+0800 GCD[1211:22656] 6)是否是主线程 1 - <NSThread: 0x6000034aa900>{number = 1, name = main}

由于使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此,看上去就像一个cpu核能够并发的执行多个线程一样;

但是,多线程编程实际上是一种易发生各种问题的编程技术。比如 多个线程更新相同的资源会导致数据不一样(数据竞争)停止等待事件的线程会导致多个线程互相持续等待(死锁)、使用太多线程会消耗大量的内存

应用程序在启动的时候,通过最先执行的线程,即主线程来描绘用户界面、处理触摸屏幕的事件等等。如果在该主线程中进行长时间的处理,比如:图像识别、数据库访问 就会妨碍主线程的执行(阻塞)。
会妨碍主线程中被称为RunLoop的主循环的执行,从而导致不能更新用户界面,应用程序的画面长时间停滞等问题
使用多线程编程,在执行长时间的处理时可以保证用户界面的响应性能

GCD的API

开发者只需要定义想要执行的任务并追加到适当的 Dispatch Queue中,GCD就能生成必要的线程并计划执行任务

    dispatch_async(queue , ^{
        //想要执行的任务
    });
  • dispatch queue 如其名所示,是执行处理的等待队列
    执行处理的等待队列 分为2种:
dispatch queue种类 说明
serial Dispatch Queue 等待现在执行中处理结束
Concurrent Dispatch Queue 不等待现在执行中处理结束
dispatch queue 创建

代码查看

    //dispatch_queue_t dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr);
    //参数一:指定SerialDispatchQueue 的名称,可以为NULL
    //参数二:生成SerialDispatchQueue 可以指定为NULL,生成Concurrent Dispatch Queue 指定为:DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t mySerialDispatchQueue  = dispatch_queue_create("等待现在执行中处理结束", NULL);
    
    dispatch_queue_t my ConcurrentlDispatchQueue  = dispatch_queue_create("等待现在执行中处理结束", DISPATCH_QUEUE_CONCURRENT);

- (void)serialDispatch
{
    NSLog(@"主线程----%@",[NSThread mainThread]);
    
    //参数一:指定SerialDispatchQueue 的名称,可以为NULL
    //参数二:生成SerialDispatchQueue 可以指定为NULL,生成Concurrent Dispatch Queue 指定为:DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t mySerialDispatchQueue  = dispatch_queue_create("等待现在执行中处理结束", NULL);
    
    //向队列中添加任务
    //在block语法中记述想执行的处理并将其追加到dispatch Queue中
    dispatch_async(mySerialDispatchQueue, ^{
        NSLog(@"下载图片1----%@",[NSThread currentThread]);
    });
    
    dispatch_async(mySerialDispatchQueue, ^{
        NSLog(@"下载图片2----%@",[NSThread currentThread]);
        
    });
    
    dispatch_async(mySerialDispatchQueue, ^{
        NSLog(@"下载图片3----%@",[NSThread currentThread]);
        
    });
    
    dispatch_async(mySerialDispatchQueue, ^{
        NSLog(@"下载图片4----%@",[NSThread currentThread]);
    });
    
    //必须要手动释放
    //名称中含有“release”的api在不需要生成的对象时,有必要通过dispatch_release函数进行释放
    //如果你部署的最低目标是 iOS 6.0 or Mac OS X 10.8 或者更高的
    //ARC已经能够管理GCD对象了,这时候,GCD对象就如同普通的OC对象一样,不应该使用dispatch_retain ordispatch_release
    //dispatch_release(mySerialDispatchQueue);
}

打印

2019-01-15 13:56:47.280855+0800 GCD[1434:32459] 主线程----<NSThread: 0x600001b31400>{number = 1, name = main}
2019-01-15 13:56:47.281106+0800 GCD[1434:32500] 下载图片1----<NSThread: 0x600001b6a940>{number = 3, name = (null)}
2019-01-15 13:56:47.281242+0800 GCD[1434:32500] 下载图片2----<NSThread: 0x600001b6a940>{number = 3, name = (null)}
2019-01-15 13:56:47.281375+0800 GCD[1434:32500] 下载图片3----<NSThread: 0x600001b6a940>{number = 3, name = (null)}
2019-01-15 13:56:47.281511+0800 GCD[1434:32500] 下载图片4----<NSThread: 0x600001b6a940>{number = 3, name =

总结:serial Dispatch Queue (等待现在执行中处理结束),使用的是一个线程,一个一个执行

当队列改为Concurrent Dispatch Queue,

    //参数一:指定SerialDispatchQueue 的名称,可以为NULL
    //参数二:生成SerialDispatchQueue 可以指定为NULL,生成Concurrent Dispatch Queue 指定为:DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t mySerialDispatchQueue  = dispatch_queue_create("等待现在执行中处理结束", DISPATCH_QUEUE_CONCURRENT);

输出如下:

2019-01-15 14:03:17.159117+0800 GCD[1514:35799] 主线程----<NSThread: 0x6000021be940>{number = 1, name = main}
2019-01-15 14:03:17.159437+0800 GCD[1514:35836] 下载图片1----<NSThread: 0x6000021edb40>{number = 3, name = (null)}
2019-01-15 14:03:17.159444+0800 GCD[1514:35833] 下载图片4----<NSThread: 0x6000021edc00>{number = 6, name = (null)}
2019-01-15 14:03:17.159463+0800 GCD[1514:35835] 下载图片2----<NSThread: 0x6000021edbc0>{number = 5, name = (null)}
2019-01-15 14:03:17.159467+0800 GCD[1514:35834] 下载图片3----<NSThread: 0x6000021edb00>{number = 4, name = (null)}
2019-01-15 14:03:17.159595+0800 GCD[1514:35836] 下载图片5----<NSThread: 0x6000021edb40>{number = 3, name = (null)}
2019-01-15 14:03:17.159623+0800 GCD[1514:35833] 下载图片6----<NSThread: 0x6000021edc00>{number = 6, name = (null)}
2019-01-15 14:03:17.159681+0800 GCD[1514:35835] 下载图片7----<NSThread: 0x6000021edbc0>{number = 5, name = (null)}
2019-01-15 14:03:17.159704+0800 GCD[1514:35834] 下载图片8----<NSThread: 0x6000021edb00>{number = 4, name = (null)}

总结:Concurrent Dispatch Queue,不等待现在执行中处理结束,使用的是多个线程,并发的执行

注意:
虽然一个serial Dispatch Queue 同时只能执行一个追加处理(一个线程),如果多个serial Dispatch Queue(多个线程) 将并发执行【前提是没有改变优先级,代码使用dispatch_set_target_queue 有介绍】
serial Dispatch Queue 使用一个线程时,数据是安全的

/**
 * 总结:会开启线程,但是只开启一个线程,同步执行
 * 当生成多个serial Dispatch Queue时,各个serial Dispatch Queue将并行执行
 */
- (void)serialDispatch02
{
    NSLog(@"主线程----%@",[NSThread mainThread]);
    
    //参数一:指定SerialDispatchQueue 的名称,可以为NULL
    //参数二:生成SerialDispatchQueue 可以指定为NULL,生成Concurrent Dispatch Queue 指定为:DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t mySerialDispatchQueue  = dispatch_queue_create("等待现在执行中处理结束", NULL);
    dispatch_queue_t mySerialDispatchQueue2  = dispatch_queue_create("等待现在执行中处理结束", NULL);
    
    dispatch_async(mySerialDispatchQueue, ^{
        NSLog(@"下载图片1----%@",[NSThread currentThread]);
    });
    
    dispatch_async(mySerialDispatchQueue2, ^{
        NSLog(@"下载图片2----%@",[NSThread currentThread]);
        
    });
    
    dispatch_async(mySerialDispatchQueue2, ^{
        NSLog(@"下载图片3----%@",[NSThread currentThread]);
        
    });
    
    dispatch_async(mySerialDispatchQueue, ^{
        NSLog(@"下载图片4----%@",[NSThread currentThread]);
        
    });
    
}

输出

2019-01-15 14:11:00.976494+0800 GCD[1579:38823] 主线程----<NSThread: 0x60000027d400>{number = 1, name = main}
2019-01-15 14:11:00.976780+0800 GCD[1579:38869] 下载图片1----<NSThread: 0x600000229640>{number = 4, name = (null)}
2019-01-15 14:11:00.976780+0800 GCD[1579:38871] 下载图片2----<NSThread: 0x600000216540>{number = 3, name = (null)}
2019-01-15 14:11:00.976956+0800 GCD[1579:38871] 下载图片3----<NSThread: 0x600000216540>{number = 3, name = (null)}
2019-01-15 14:11:00.977072+0800 GCD[1579:38869] 下载图片4----<NSThread: 0x600000229640>{number = 4, name = (null)}
全局队列queue、主队列queue

Main Dispatch Queue 在主线程执行的Dispatch Queue,因为主现场只有一个,所以Main Dispatch Queue 自然就是serialDispatch queue,串行执行

- (void)mainDispatchQueue
{
    dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

    dispatch_async(mainDispatchQueue, ^{
        NSLog(@"在mainDispatchQueue 里面,当前现场---%@",[NSThread currentThread]);
        NSLog(@"在mainDispatchQueue 里面,主线程----%@",[NSThread mainThread]);
        
    });
}

输出如下: 都是主线程

2019-01-15 14:21:18.661956+0800 GCDDemo01[1682:42652] 在mainDispatchQueue 里面,当前现场---<NSThread: 0x600000cd9400>{number = 1, name = main}
2019-01-15 14:21:18.662125+0800 GCDDemo01[1682:42652] 在mainDispatchQueue 里面,主线程----<NSThread: 0x600000cd9400>{number = 1, name = main}

全局队列 Global Dispatch Queu:block块里面是并行执行的

4个优先级 如下:

Global Dispatch Queue种类 说明
DISPATCH_QUEUE_PRIORITY_HIGH 执行优先级: 最高
DISPATCH_QUEUE_PRIORITY_DEFAULT 执行优先级: 默认
DISPATCH_QUEUE_PRIORITY_LOW 执行优先级: 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND 执行优先级: 后台

创建方法

    //第二个参数 都是0
    dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
    dispatch_async(globalDispatchQueue, ^{
        NSLog(@"当前现场---%@",[NSThread currentThread]);
        NSLog(@"主线程----%@",[NSThread mainThread]);

        //可并行执行的处理
        //...
        
        dispatch_async(mainDispatchQueue, ^{
            //只能在主线程中执行的处理
            //...
            [self.btn setTitle:@"load..." forState:UIControlStateNormal];
        });
    });

打印如下:

2019-01-15 14:32:22.606748+0800 GCDDemo01[1752:46356] 当前现场---<NSThread: 0x600000bc2600>{number = 3, name = (null)}
2019-01-15 14:32:22.607040+0800 GCDDemo01[1752:46356] 主线程----<NSThread: 0x600000ba6940>{number = 1, name = (null)}
变更优先级 dispatch_set_target_queue

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t _Nullable queue);

第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。

dispatch_queue_create函数生成的DisPatch Queue不管是Serial DisPatch Queue还是Concurrent Dispatch Queue,执行的优先级都与默认优先级的Global Dispatch queue相同,
如果需要变更生成的Dispatch Queue的执行优先级则需要使用dispatch_set_target_queue函数

  • 变更优先级
    //串行队列
    dispatch_queue_t serialDiapatchQueue=dispatch_queue_create("com.GCD_demo.www", DISPATCH_QUEUE_SERIAL);
      
     //全局队列
    dispatch_queue_t dispatchgetglobalqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    
    //使串行队列 与全局队列优先级low 相同
    dispatch_set_target_queue(serialDiapatchQueue, dispatchgetglobalqueue);
    
    
    dispatch_async(serialDiapatchQueue, ^{
        NSLog(@"我优先级低,先让让");
    });
    
    //   serialDiapatchQueue  优先级< 新的全局队列

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"我优先级高,我先block");
    });
    

打印

2019-01-15 14:49:46.603854+0800 GCDDemo01[1874:51890] 我优先级高,我先block
2019-01-15 14:49:46.603884+0800 GCDDemo01[1874:51891] 我优先级低,先让让
  • 使用dispatch_set_target_queue将多个串行的queue指定到了同一目标,那么着多个串行queue在目标queue上就是同步执行的,不再是并行执行。
 //1.创建目标队列
    dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
    
    //2.创建3个串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
    
    //3.将3个串行队列分别添加到目标队列
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_set_target_queue(queue3, targetQueue);
    
    
    dispatch_async(queue1, ^{
        NSLog(@"1 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"1 out");
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"2 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"2 out");
    });
    dispatch_async(queue3, ^{
        NSLog(@"3 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"3 out");
    });

打印如下: 不再是并行执行。

2019-01-15 14:58:52.260714+0800 GCDDemo01[1945:55274] 1 in
2019-01-15 14:58:55.266195+0800 GCDDemo01[1945:55274] 1 out
2019-01-15 14:58:55.266507+0800 GCDDemo01[1945:55274] 2 in
2019-01-15 14:58:57.267504+0800 GCDDemo01[1945:55274] 2 out
2019-01-15 14:58:57.267880+0800 GCDDemo01[1945:55274] 3 in
2019-01-15 14:58:58.273349+0800 GCDDemo01[1945:55274] 3 out

Serial DisPatch Queue是一个串行队列,只能同时执行1个追加处理(即任务),当用Dispatch_queue_create函数生成多个Serial DisPatch Queue时,每个Serial DisPatch Queue均获得一个线程,即多个Serial DisPatch Queue可并发执行,同时处理添加到各个Serial DisPatch Queue中的任务

但要注意如果过多地使用多线程,就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能,所以我们只在为了避免多个线程更新相同资源导致数据竞争时,使用Serial DisPatch Queue。

dispatch_after
- (void)dispatchAfter
{
    NSLog(@"开始调用");
    //DISPATCH_TIME_NOW: 表示现在的时间
    //ull:是C语言的数值字面量,是显式表示类型时使用的字符串(表示“unsigned long long”)
    //NSEC_PER_MSEC: 表示可以以毫秒为单位计算
    //
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
    
    //在3s后用dispatch_async 函数追加block到main dispatch Queue
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"延迟调用");
    });
    
    //dispatch_walltime 函数用于计算绝对时间
    //dispatch_time 函数用于计算相对时间
    //dispatch_walltime(<#const struct timespec * _Nullable when#>, <#int64_t delta#>)
}
dispatch_Group

在开发过程中,在追加到dispathc Queue 中的多个处理全部结束后想执行结束处理,这种情况经常出现。那么可以引入dispatch_Group

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    //与dispatch_asyn 不同的是指定生成的dispatch_group_t 为第一参数。指定的block属于指定的group
    dispatch_group_async(group, queue, ^{
        NSLog(@"组一   %@",[NSThread currentThread] );
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"组二   %@",[NSThread currentThread] );
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"组三   %@",[NSThread currentThread] );
        sleep(10);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"执行完毕   %@",[NSThread currentThread] );
    });
    
    
    //第二参数:指定为等待的时间(超时)
    //DISPATCH_TIME_FOREVER  永久等待,只要属于group 的处理尚未执行介绍,就会一直等待,中途不能取消
    //DISPATCH_TIME_NOW 不等待即可判断属于group 的处理是否执行结束
    //long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);
    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    if (result == 0) {
        NSLog(@"属于group 的全部处理执行结束");
    } else {
        NSLog(@"属于group 的某一个处理还在执行");
    }
    
    NSLog(@"主线程----%@",[NSThread mainThread]);

打印数据如下:

2019-01-15 15:32:28.715196+0800 GCDDemo01[2208:65352] 组三   <NSThread: 0x6000032872c0>{number = 5, name = (null)}
2019-01-15 15:32:28.715196+0800 GCDDemo01[2208:65354] 组二   <NSThread: 0x600003287200>{number = 4, name = (null)}
2019-01-15 15:32:28.715196+0800 GCDDemo01[2208:65353] 组一   <NSThread: 0x6000032bce40>{number = 3, name = (null)}
2019-01-15 15:32:38.716752+0800 GCDDemo01[2208:65320] 属于group 的全部处理执行结束
2019-01-15 15:32:38.716945+0800 GCDDemo01[2208:65320] 主线程----<NSThread: 0x6000032d9400>{number = 1, name = main}
2019-01-15 15:32:38.747027+0800 GCDDemo01[2208:65320] 执行完毕   <NSThread: 0x6000032d9400>{number = 1, name = main}

那么这个能够解决在开发过程中,一个控制器里面,有2个异步网络请求,当所有的请求结束后刷新页面呢?
如下:

- (void)dispatchGroupDemo
{
    NSURLSession *session = [NSURLSession sharedSession];
    
    dispatch_queue_t dispatchQueue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t dispatchGroup = dispatch_group_create();
   
    dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
        
        NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"got data from internet1");
        }];
        [task resume];
    });
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
        NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"got data from internet2");
        }];
        [task resume];
    });
    
    dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
        NSLog(@"end");
    });
    
    /**
     上述输出结果:显然没有的达到要求
     这是因为这里的网络请求是个异步的方法,没有等待具体的数据返回,放入的dispatch queue的 block就执行完毕了。所以没收到2个网络数据,就提前调用了dispatch_group_notify指定的结束方法。
     
     2018-08-21 17:36:45.729321+0800 GCDDemo01[97318:3993362] end
     2018-08-21 17:36:45.832798+0800 GCDDemo01[97318:3993495] got data from internet2
     2018-08-21 17:36:45.870109+0800 GCDDemo01[97318:3993455] got data from internet1
     */

}

很明显,这个方法行不通, 这是因为这里的网络请求是个异步的方法,没有等待具体的数据返回,放入的dispatch queue的 block就执行完毕了。所以没收到2个网络数据,就提前调用了dispatch_group_notify指定的结束方法。

下面这个勉强能行

- (void)dispatchGroupDemo02
{
    NSURLSession *session = [NSURLSession sharedSession];
    dispatch_group_t dispatchGroup = dispatch_group_create();
   
    
    //手动指示一个block块已进入组
    //调用此函数表示另一个block块已通过除 dispatch_group_async() 之外的其他方法加入该组。对这个函数的调用必须与dispatch_group_leave()进行平衡。
    dispatch_group_enter(dispatchGroup);
    NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"got data from internet1");
        
        dispatch_group_leave(dispatchGroup);
    }];
    [task resume];
    
    
    dispatch_group_enter(dispatchGroup);
    NSURLSessionDataTask *task2 = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"got data from internet2");
        
        dispatch_group_leave(dispatchGroup);
    }];
    [task2 resume];
    
    dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
        NSLog(@"end");
    });
    
    /**
     输出结果:
     2018-08-21 17:41:02.463886+0800 GCDDemo01[97790:4012417] got data from internet1
     2018-08-21 17:41:02.493294+0800 GCDDemo01[97790:4012421] got data from internet2
     2018-08-21 17:41:02.493488+0800 GCDDemo01[97790:4012365] end
     
     相对于简单的dispatch_group_async,dispatch_group_enter 和 dispatch_group_leave 可以对group进行更细致的处理。
     
     dispatch_group_enter:(上面已有解释)
     简单的说,就是dispatch_group_enter会对group的内部计数加一,dispatch_group_leave会对group的内部计数减一,就类似以前的retain和release方法。说白了也是维护了一个计数器。
     
     以前我的做法就是自己维护计数器。在发送网络请求前,记下发送总数,数据返回后,在同一个thread中(或者在一个DISPATCH_QUEUE_SERIAL类型的dispatch_queue中),对计数器进行+1操作,当计数器和网络请求数相等时,调用最后的处理。
     
     相比自己的处理的计数器,dispatch_group_enter 处理方法可能显得更正规一些,代码更规范了,但执行效果是一样的。。。
     
     
     
     今天再改其他的工程的时候,又遇到了这个问题,有一个值,需要2个异步操作查询回2个值进行计算,因此必须再2个异步操作结束后才能进行计算操作。开始试着使用了OperationQueue,想用addDependency方法,但是这个方法无法灵活地控制,只适合block内容已经确定的情况。对于我遇到的这种异步操作,block的内容是不定的,需要依赖异步的返回,用operation queue会遇到各种问题,无法解决问题,十分复杂!
     
     */
}

dispatch_barrier_async

在访问数据库或文件时,使用同步队列Serial dispatch queue 可避免数据竞争问题,
写入处理确实不可与其他写入处理以及包含读取处理的其他某些处理并行执行。但是如果读取处理只是与读取粗粝并行执行,这是不会有问题的;
为了高校的访问,读取处理追加到 并行队列Concurrent dispatch queue 中,写入处理在任意一个读取处理没有执行的状态下,追加到Serial dispatch queue 中即可(写入处理之前,读取处理不可执行)

为了解决这个问题,引入:dispatch_barrier_async

   dispatch_queue_t queue = dispatch_queue_create("label1", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"异步一 %@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"异步二 %@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"异步三 %@",[NSThread currentThread]);
    });
    
    
    //dispatch_barrier_async函数:
    //会等待追加到 并行队列queue 上的并行执行的处理全部结束以后,再将指定的处理追加到该 并行队列queue 中,
    //然后再由dispatch_barrier_async函数追加的处理执行完毕后, 并行队列queue 才恢复为一般的动作
    //简单地说,就是在这个函数之前被提交到quque里的block一定会被先执行,之后执行dispatch_barrier_async设定的block,最后执行调用dispatch_barrier_async之后才提交到queue里的block。
    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch_barrier_async");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"异步四 %@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"异步五 %@",[NSThread currentThread]);
    });
    
    
    dispatch_async(queue, ^{
        NSLog(@"异步六 %@",[NSThread currentThread]);
    });

打印如下

2019-01-15 16:00:16.373259+0800 GCDDemo01[2440:75032] 异步一 <NSThread: 0x600003f4edc0>{number = 3, name = (null)}
2019-01-15 16:00:16.373259+0800 GCDDemo01[2440:75031] 异步三 <NSThread: 0x600003f70f40>{number = 5, name = (null)}
2019-01-15 16:00:16.373261+0800 GCDDemo01[2440:75033] 异步二 <NSThread: 0x600003f4f140>{number = 4, name = (null)}
2019-01-15 16:00:16.373659+0800 GCDDemo01[2440:75032] dispatch_barrier_async
2019-01-15 16:00:16.373819+0800 GCDDemo01[2440:75031] 异步五 <NSThread: 0x600003f70f40>{number = 5, name = (null)}
2019-01-15 16:00:16.373840+0800 GCDDemo01[2440:75032] 异步四 <NSThread: 0x600003f4edc0>{number = 3, name = (null)}
2019-01-15 16:00:16.373819+0800 GCDDemo01[2440:75033] 异步六 <NSThread: 0x600003f4f140>{number = 4, name = (null)}

dispatch_barrier_async函数:
1)会等待追加到 并行队列queue 上的并行执行的处理全部结束以后,再将指定的处理追加到该 并行队列queue 中,
2)然后再由dispatch_barrier_async函数追加的处理执行完毕后, 并行队列queue 才恢复为一般的动作
3)简单地说,就是在这个函数之前被提交到quque里的block一定会被先执行,之后执行dispatch_barrier_async设定的block,最后执行调用dispatch_barrier_async之后才提交到queue里的block。

dispatch_sync

同步:

 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //一旦调用dispatch_sync函数,那么指定的处理执行结束之前,该函数不会返回(返回的意思就是从队列你们出来)
    dispatch_sync(globalQueue, ^{
        NSLog(@"同步操作 并行队列1  %@",[NSThread currentThread]);
    });
    
    dispatch_sync(globalQueue, ^{
        NSLog(@"同步操作 并行队列2  %@",[NSThread currentThread]);
    });
    
    dispatch_sync(globalQueue, ^{
        NSLog(@"同步操作 并行队列3  %@",[NSThread currentThread]);
    });
    
    dispatch_sync(globalQueue, ^{
        NSLog(@"同步操作 并行队列4  %@",[NSThread currentThread]);
    });
    
    NSLog(@"同步操作 并行队列5  %@",[NSThread currentThread]);

打印: 都在主线程

2019-01-15 16:19:55.460438+0800 GCDDemo01[2669:83775] 同步操作 并行队列1  <NSThread: 0x600000266e80>{number = 1, name = main}
2019-01-15 16:19:55.460683+0800 GCDDemo01[2669:83775] 同步操作 并行队列2  <NSThread: 0x600000266e80>{number = 1, name = main}
2019-01-15 16:19:55.460814+0800 GCDDemo01[2669:83775] 同步操作 并行队列3  <NSThread: 0x600000266e80>{number = 1, name = main}
2019-01-15 16:19:55.460953+0800 GCDDemo01[2669:83775] 同步操作 并行队列4  <NSThread: 0x600000266e80>{number = 1, name = main}
2019-01-15 16:19:55.461079+0800 GCDDemo01[2669:83775] 同步操作 并行队列5  <NSThread: 0x600000266e80>{number = 1, name = main}

为什么都是在主线程里面执行?

这是一个 同步函数+并发队列
这些任务都是创建一个就立马执行,执行完才创建下一个,因为是同步函数,所以不新建线程,只要是同步函数,就不会新建线程。
并发队列与否,并不影响同步函数的创建,因为本身就不能多创建线程,也就不存在并发

容易造成死锁

 //同步函数+串队列
    NSLog(@"死锁问题输出01  %@",[NSThread currentThread]);
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_sync(mainQueue, ^{ //崩溃到这一步
        NSLog(@"死锁问题输出02  %@",[NSThread currentThread]);
    });
    NSLog(@"死锁问题输出03  %@",[NSThread currentThread]);

原因:(查看很多博客,五花八门,解释的很多不一样,但总的一点就是互相阻塞)
下面是我总结的一个说法:

 总结:
 一旦调用dispatch_sync函数,那么指定的处理执行结束之前,该函数不会返回(返回的意思就是从队列你们出来)
 相反dispatch_async这个函数是开一条线程出来执行Block,不等Block返回就直接返回了。

 我们通常把任务也就是所说的block块(swift 叫闭包)交给GCD函数,函数把任务添加到队列queue(先进先出的原则)里面
 队列遵循严格的先进后出的原则,同一个queue中,最早入列的block,会最早的分配给线程去执行
 
 GCD队列只是组织待执行任务的一个数据结构封装,而线程才是执行任务的人
 
 串行队列:队列中的任务是一个执行完成后才去执行另一个
 解释:
 1)主线程调用dispatch_sync这个函数               【这一步把dispatch_sync 函数本身 进入main队列,记做A任务】
 2)当这个函数返回(执行完毕)的时候主线程才能往下执行  【dispatch_sync 函数执行完毕才能出main队列,才能继续往下执行,执行B任务,也就是block块】
 3)但dispatch_sync返回的条件是里面的Block返回,里面的Block是不会执行的,因为它是被插到主队列最后执行,然而因为dispatch_sync无法返回,所以主队列无法执行到最后一个任务。                      【执行block任务,记做B任务,它在任务A后面,必须等待任务A执行完毕,才能执行B】

 【A任务 等待B任务执行结束后才能出main队列,但是串行main队列中(同步函数),B任务在A任务后面,A任务不完成,是不会执行B任务的,从而造成互相阻塞,从而死锁】
 
 如果队列 改为 自行创建的串行队列 dispatch_queue_create("d", NULL),不会造成死锁,为什么?

 因为: block块添加的队列 是自行创建的队列,跟dispatch_sync函数添加到的mian队列不是同一个,blocl块添加到队列里,是队列的头也是尾,所以先执行并返回,然后dispatch_sync返回,继续执行dispatch_sync函数下面的语句

图解:
死锁分析1.png
死锁分析2.png
dispatch_apply
  • dispatch_apply 函数时dispatch_sync 函数和dispatch Groupd的关联API
  • dispatch_apply 函数按指定的次数将指定的block 追加到指定的dispatch_queue_t 中,并等待全部处理执行结束
    //参数一: 重复次数
    //参数二:追加对象的queue
    //参数三:追加的处理
    dispatch_apply(10, global, ^(size_t index) {
        NSLog(@"%zu",index);
        //sleep(2);
    });
    
    NSLog(@"done");

输出

2019-01-15 17:05:15.313947+0800 GCDDemo01[2990:98143] 1
2019-01-15 17:05:15.313947+0800 GCDDemo01[2990:98097] 0
2019-01-15 17:05:15.313949+0800 GCDDemo01[2990:98134] 2
2019-01-15 17:05:15.313977+0800 GCDDemo01[2990:98137] 3
2019-01-15 17:05:15.314154+0800 GCDDemo01[2990:98097] 7
2019-01-15 17:05:15.314154+0800 GCDDemo01[2990:98143] 6
2019-01-15 17:05:15.314155+0800 GCDDemo01[2990:98134] 5
2019-01-15 17:05:15.314155+0800 GCDDemo01[2990:98137] 4
2019-01-15 17:05:15.314260+0800 GCDDemo01[2990:98143] 8
2019-01-15 17:05:15.314268+0800 GCDDemo01[2990:98097] 9
2019-01-15 17:05:15.314756+0800 GCDDemo01[2990:98097] done

因为全局队列,所以输出结果 不确定,但最后 done 输出必定在最后的位置
这是因为dispatch_apply函数会等待全部处理执行结束

  • 在dispatch_async函数中异步执行dispatch_apply函数,模拟dispatch_sync的同步效果
- (void)dispatch_apply03
{
    //在dispatch_async函数中异步执行dispatch_apply函数,模拟dispatch_sync的同步效果
    NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^{
        NSLog(@"执行一");
        
        //等待dispatch_apply 函数中的全部处理执行结束
        dispatch_apply([array count], queue, ^(size_t index) {
            
            //处理包含在NArrray对象的全部对象
            NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
        });
        
        NSLog(@"执行二");
        //在main dispatch queue 中非同步执行
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主线程执行用户界面更新等操作");
        });
        
    });
}
    2019-01-15 17:10:11.744106+0800 GCDDemo01[3034:99956] 执行一
     2019-01-15 17:10:11.744316+0800 GCDDemo01[3034:99956] 0: a
     2019-01-15 17:10:11.744419+0800 GCDDemo01[3034:99956] 1: b
     2019-01-15 17:10:11.744516+0800 GCDDemo01[3034:99956] 2: c
     2019-01-15 17:10:11.744601+0800 GCDDemo01[3034:99956] 3: d
     2019-01-15 17:10:11.744695+0800 GCDDemo01[3034:99956] 4: e
     2019-01-15 17:10:11.744778+0800 GCDDemo01[3034:99956] 5: f
     2019-01-15 17:10:11.744881+0800 GCDDemo01[3034:99956] 6: g
     2019-01-15 17:10:11.745772+0800 GCDDemo01[3034:99956] 7: h
     2019-01-15 17:10:11.746091+0800 GCDDemo01[3034:99956] 8: i
     2019-01-15 17:10:11.746418+0800 GCDDemo01[3034:99956] 9: j
     2019-01-15 17:10:11.746750+0800 GCDDemo01[3034:99956] 执行二
     2019-01-15 17:10:11.759049+0800 GCDDemo01[3034:99913] 回到主线程执行用户界面更新等操作
dispatch_suspend

有时候希望不执行已追加的处理 从而引入dispatch_suspend

dispatch_suspend函数: 挂起指定的dispatch_queue
dispatch_resume函数: 恢复指定的dispatch_queue
注意: 对已经执行的处理没有影响,挂起后,追加到dispatch_queue 中尚未执行处理的,在此之后停止执行。而恢复则使得这些处理能够继续执行

 dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
    //提交第一个block,延时5秒打印。
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds...");
    });
    
    //提交第二个block,也是延时5秒打印
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds again...");
    });
    
    //延时一秒
    NSLog(@"sleep 1 second...");
    sleep(1);
   
    //挂起队列
    NSLog(@"suspend...");
    dispatch_suspend(queue);
    
    //延时10秒
    NSLog(@"sleep 10 second...");
    sleep(10);
    
    //恢复队列
    NSLog(@"resume...");
    dispatch_resume(queue);

打印如下:

2019-01-15 17:16:51.256678+0800 GCDDemo01[3094:102562] sleep 1 second...
2019-01-15 17:16:52.257143+0800 GCDDemo01[3094:102562] suspend...
2019-01-15 17:16:52.257403+0800 GCDDemo01[3094:102562] sleep 10 second...
2019-01-15 17:16:56.260057+0800 GCDDemo01[3094:102606] After 5 seconds...
2019-01-15 17:17:02.258408+0800 GCDDemo01[3094:102562] resume...
2019-01-15 17:17:07.262380+0800 GCDDemo01[3094:102603] After 5 seconds again...
dispatch_semaphore_t 信号量

dispatch_semaphore_t 是持有计数的信号,该计数是多线程编程中的计数类型信号。

  • 创建信号
//参数表示计数的初始值
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
  • 等待信号量
    【当计数值>=1,或者在待机中计数值>= 1,对该计数进行减法并从dispatch_semaphore_wait函数返回; 返回值==0时,可以安全的执行需要进行怕他控制的处理】
//long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

//时间
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
  • dispatch_semaphore_signal 提高信号量
dispatch_semaphore_signal(semaphore);
    //计数值 ==1
    //保证可访问数组NSMutableArray类对象的线程只能有一个
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSMutableArray *marray = [NSMutableArray new];
    for (int i = 0; i < 100000; i ++) {
        dispatch_async(global, ^{
            
            //等待dispatch_semaphore,
            //一直等待,知道dispatch_semaphore 技术 >= 1
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            
            //由于dispatch_semaphore 技术>= 1,所以将dispatch_semaphore 计数值-1
            //dispatch_semaphore_wait 函数执行返回,到此 计数为0
            //可以安全w访问
            [marray addObject:[NSNumber numberWithInteger:i]];
            
            //排他控制处理结束
            //所以通过dispatch_semaphore_signal 函数 将dispatch_semaphore 计数 +1
            dispatch_semaphore_signal(semaphore);
            
        });
    }
    
  • 我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。

-(void)dispatchSignal{
    //crate的value表示,最多几个资源可访问
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //任务1
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);
    });
    //任务2
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);
    });
    
    //任务3
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);
    });
}

打印如下:

2019-01-15 18:52:27.165995+0800 GCDDemo01[3896:134773] run task 1
2019-01-15 18:52:27.166000+0800 GCDDemo01[3896:134771] run task 3
2019-01-15 18:52:28.171006+0800 GCDDemo01[3896:134773] complete task 1
2019-01-15 18:52:28.171014+0800 GCDDemo01[3896:134771] complete task 3
2019-01-15 18:52:28.171393+0800 GCDDemo01[3896:134774] run task 2
2019-01-15 18:52:29.176322+0800 GCDDemo01[3896:134774] complete task 2

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

  • 当 创建信号量=1时 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

输出:

2019-01-15 18:54:22.922276+0800 GCDDemo01[3935:136035] run task 1
2019-01-15 18:54:23.925043+0800 GCDDemo01[3935:136035] complete task 1
2019-01-15 18:54:23.925346+0800 GCDDemo01[3935:136038] run task 2
2019-01-15 18:54:24.927118+0800 GCDDemo01[3935:136038] complete task 2
2019-01-15 18:54:24.927407+0800 GCDDemo01[3935:136036] run task 3
2019-01-15 18:54:25.932808+0800 GCDDemo01[3935:136036] complete task 3

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

对比下面两个方法的差异:

- (NSString *)sting
{
    __block NSString *s = nil;
    dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    dispatch_async(globalDispatchQueue, ^{
        s = @"123";
    });
    
    return s;
}
- (NSString *)sting_dispatch_semaphore_t
{
    __block NSString *s = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    dispatch_async(globalDispatchQueue, ^{
        
        s = @"123";
        dispatch_semaphore_signal(semaphore);

    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return s;
}

技术信号的形式,等待异步执行的结果,并将结果返回.

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

dispatch_once_t 一般创建单例
  • 保证应用程序只能执行一次制定处理的api
- (void)dispatchOnce
{
    // 保证程序只执行一次,一般用于生成单例模式
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
    });
}
Dispatch I/O
  • 提高读取速率,尝试使用Dispatch I/O

GCD实现

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

推荐阅读更多精彩内容