iOS - GCD

目录

  • GCD简介
  • GCD核心概念
  • GCD队列的使用
  • GCD的常见面试题

GCD简介

Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。

脑图

GCD整体结构
GCD相关的一些基本概念
GCD 信号量:dispatch_semaphore

优点

  • 可用于多核的并行运算;
  • 会自动利用更多的CPU内核(比如双核、四核);
  • 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。
  • 抽象层次最高,使用也简单,因此,苹果也推荐使用GCD。

缺点:

  • 使用不当容易造成死锁

GCD核心概念

任务和队列

  • 任务:就是你在线程中执行的那段代码,放在GCD的Block块中。执行任务有2种方式:
    同步执行(sync)异步执行(async)
同步执行(sync):
1. 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
2. 只能在当前线程中执行任务,不具备开启新线程的能力。
异步执行(async):
1. 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
2. 可以在新的线程中执行任务,具备开启新线程的能力。
通俗解释:
人物:你,我;
事件:吃饭;
同步:你叫我吃饭,我听到回复你,然后一起去吃饭。如果我没有听到或者回复你,你就会一直叫我去吃饭,一直等我,直到我和你一起去吃饭为止。
异步:你去吃饭,喊了我一声,然后自己直接去吃饭了,我的行为不会对你造成影响。我可以和你一起去吃,也可以等我把手头的事情忙完再去吃。这之间你可以完成很多事情,不会受到我的影响。

区别:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

  • 队列(Dispatch Queue):队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
    GCD有2种队列:串行队列(Serial Dispatch Queue)并发队列(Concurrent Dispatch Queue)
串行队列(Serial Dispatch Queue):每次只执行一个任务,当前一个任务执行完成后才执行下一个任务。
并发队列(Concurrent Dispatch Queue):多个任务并发执行,所以先执行的任务可能最后才完成(因为具体的执行过程导致)。

区别:执行顺序不同,以及开启线程数不同。
具体区别如下两图所示(图片来源网络):

串行队列

并行队列

关于同步异步、串行并行和线程的关系,下面通过一个表格来总结:

由上图我们可以得出我们有3种队列,2种任务执行方式,那么我们就有了6种不同的组合方式,分别是:

1.并行队列 + 同步执行
2.并行队列 + 异步执行
3.串行队列 + 同步执行
4.串行队列 + 异步执行
5.主队列 + 同步执行
6.主队列 + 异步执行

那么这几种不同组合方式各有什么区别:

队列 并发队列 串行队列 主队列
同步 (sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 主线程调用:死锁卡住不执行其他线程调用:没有开启新线程,串行执行任务
异步 (async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

需要注意的几个地方:

  • 异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。
  • 并发队列的并发功能只有在异步(dispatch_async)函数下才有效。
  • 主队列是一种特殊的串行队列。

GCD队列的使用

  • 队列的创建:
    可以使用dispatch_queue_create来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并行队列。
// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_SERIAL);
// 并行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
// 全局并行队列。GCD默认提供了全局的并行队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    });
  • 队列的获取(这里说的是两个特殊的队列,主队列和全局并发队列):
主队列:
dispatch_queue_t queue = dispatch_get_main_queue();
并发队列:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  • 任务的创建:
// 同步执行任务创建方法
dispatch_sync(queue, ^{
 // 执行的任务
    NSLog(@"%@",[NSThread currentThread]);   
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
 // 执行的任务
    NSLog(@"%@",[NSThread currentThread]);    
});

需要注意的几个地方:

  1. 所有放在主队列中的任务,都会放到主线程中执行。
  2. GCD 默认提供了全局并发队列(Global Dispatch Queue)。
  • 6种不同的组合方式:
    1.并发队列 + 同步执行
    特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
//并发队列 + 同步执行
- (void) syncConcurrent
{
    NSLog(@"begin-- 并发队列 + 同步执行");
    dispatch_queue_t queue= dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 并发队列 + 同步执行");
}

输出的结果:
2018-09-20 17:15:06.116567+0800 GCDDemo[2295:1059495] begin-- 并发队列 + 同步执行
2018-09-20 17:15:06.116978+0800 GCDDemo[2295:1059495] 1------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117130+0800 GCDDemo[2295:1059495] 1------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117269+0800 GCDDemo[2295:1059495] 2------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117387+0800 GCDDemo[2295:1059495] 2------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117467+0800 GCDDemo[2295:1059495] 3------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117536+0800 GCDDemo[2295:1059495] 3------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117609+0800 GCDDemo[2295:1059495] end-- 并发队列 + 同步执行

从打印中可以总结如下几点:

  • 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。
  • 所有任务按顺序执行的。
  • 所有任务都在打印的begin--和end--之间,由于同步任务需要等待队列的任务执行结束才能执行下一个任务。

疑问点:并发队列具备开启多个线程能力为什么没不能同时执行任务呢?
:虽然并发队列可以开启多个线程,并且可以同时执行多个任务。但是其本身不能创建新线程,因为同步任务不具备开启新线程的能力,所以只有当前线程这一个线程,所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。

2.并发队列 + 异步执行
特点:可以开启多个线程,任务交替(同时)执行。

// 并发队列 + 异步执行
- (void) asyncConcurrent
{
    NSLog(@"begin-- 并发队列 + 异步执行");
    dispatch_queue_t queue= dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"end-- 并发队列 + 异步执行");
}

输出的结果:
2018-09-20 17:41:42.367277+0800 GCDDemo[2336:1126287] begin-- 并发队列 + 异步执行
2018-09-20 17:41:42.367446+0800 GCDDemo[2336:1126287] end-- 并发队列 + 异步执行
2018-09-20 17:41:42.367505+0800 GCDDemo[2336:1126603] 2------<NSThread: 0x60000083a280>{number = 4, name = (null)}
2018-09-20 17:41:42.367517+0800 GCDDemo[2336:1126606] 3------<NSThread: 0x600000827d00>{number = 5, name = (null)}
2018-09-20 17:41:42.367522+0800 GCDDemo[2336:1126604] 1------<NSThread: 0x600000827c00>{number = 3, name = (null)}
2018-09-20 17:41:42.367601+0800 GCDDemo[2336:1126603] 2------<NSThread: 0x60000083a280>{number = 4, name = (null)}
2018-09-20 17:41:42.367627+0800 GCDDemo[2336:1126606] 3------<NSThread: 0x600000827d00>{number = 5, name = (null)}
2018-09-20 17:41:42.367653+0800 GCDDemo[2336:1126604] 1------<NSThread: 0x600000827c00>{number = 3, name = (null)}

从打印中可以总结如下几点:

  • 除了主线程,又开启了3个线程,并且任务是交替着同时执行的,由于异步执行具备开启新线程的能力,且并发队列可开启多个线程,同时执行多个任务.
  • 所有任务是在打印的begin-- 和end-- 之后才开始执行的,说明任务不是马上执行,而是将所有任务添加到队列之后才开始异步执行,另外当前线程并没有等待,而是直接开启了新的线程,在新线程中执行任务,由于异步执行不做等待,所以可以继续执行其他任务.

3.串行队列 + 同步执行
特点:不会开启新线程,在当前线程执行任务,并且任务是串行的,执行完一个任务,再执行下一个任务。

// 串行队列 + 同步执行
- (void) syncSerial
{
    NSLog(@"begin-- 串行队列 + 同步执行");
    dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 串行队列 + 同步执行");
}

输出的结果:
2018-09-21 09:14:51.305711+0800 GCDDemo[1205:63899] begin-- 串行队列 + 同步执行
2018-09-21 09:14:51.305958+0800 GCDDemo[1205:63899] 1------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306117+0800 GCDDemo[1205:63899] 1------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306202+0800 GCDDemo[1205:63899] 2------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306292+0800 GCDDemo[1205:63899] 2------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306431+0800 GCDDemo[1205:63899] 3------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306516+0800 GCDDemo[1205:63899] 3------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306626+0800 GCDDemo[1205:63899] end-- 串行队列 + 同步执行

从打印中可以总结如下几点:

  • 所有任务都是在主线程中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力),由于串行队列,所以按顺序一个一个执行下去。
  • 所有任务都在打印的begin-- 和end-- 之间,这说明任务是添加到队列中马上执行的,而且同步任务需要等待队列的任务执行结束,才可以执行下一个任务。
  • 任务是按顺序执行的,这说明串行队列每次只有一个任务被执行,任务一个接一个按顺序执行下去。

4.串行队列 + 异步执行
特点:会开启新线程,但是因为任务是串行的,所以执行完一个任务之后,才会再执行下一个任务。

//串行队列 + 异步执行
- (void)asyncSerial {

       NSLog(@"begin-- 串行队列 + 异步执行");
    dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
               NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
               NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
       NSLog(@"3------%@",[NSThread currentThread]);
        }
    });  
    NSLog(@"end-- 串行队列 + 异步执行");

}

输出的结果:
2018-09-21 09:35:45.691409+0800 GCDDemo[1363:134940] begin-- 串行队列 + 异步执行
2018-09-21 09:35:45.691612+0800 GCDDemo[1363:134940] end-- 串行队列 + 异步执行
2018-09-21 09:35:45.691686+0800 GCDDemo[1363:135267] 1------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
2018-09-21 09:35:45.691787+0800 GCDDemo[1363:135267] 1------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
2018-09-21 09:35:45.691886+0800 GCDDemo[1363:135267] 2------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
2018-09-21 09:35:45.691959+0800 GCDDemo[1363:135267] 2------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
2018-09-21 09:35:45.692040+0800 GCDDemo[1363:135267] 3------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
2018-09-21 09:35:45.692107+0800 GCDDemo[1363:135267] 3------<NSThread: 0x6000016fc980>{number = 3, name = (null)}

从打印中可以总结如下几点:

  • 开启了一条新线程(异步执行具备开启新线程的能力,但是串行队列只能开启一个线程),但是任务由于是串行的,所以任务还是一个一个的执行下去(串行队列每次只能有一个任务被执行,任务一个接着一个顺序执行)。
  • 所有任务是在打印的begin-- 和end-- 之后才开始执行的,说明任务是将所有任务添加到队列之后才开始同步执行,而不是马上执行(异步执行不会做任何等待,可以继续执行任务)。

5.主队列 + 同步执行
特点:1.主线程调用:互等卡主不执行。 2.其他线程调用:不会开启新线程,执行完一个任务,再执行下一个任务。

  • 主线程调用
//主队列 + 同步执行  
- (void)syncMain
{
    NSLog(@"begin-- 主队列 + 同步执行");
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 主队列 + 同步执行");
}

输出的结果:
2018-09-21 10:02:33.294677+0800 GCDDemo[1563:202647] begin-- 主队列 + 同步执行
(lldb) 

从打印中可以总结如下:

  • 程序崩溃,只打印出begin-- 主队列 + 同步执行

疑问点:为什么任务没有执行完,而且程序了崩溃呢?
:我们在主线程中执行syncMain方法,相当于把syncMain任务放到了主线程的队列中,主线程正在处理syncMain这个任务syncMain方法中又有同步事件需要处理 ,造成会相互等待,所以死锁了,所以任务不会执行完。

  • 其他线程调用
// 我们先创建了一个异步并发队列,然后在队列中调用syncMain方法。
dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
            NSLog(@"当前线程:%@",[NSThread currentThread]);
        [self syncMain];
    });
输出的结果:
2018-09-21 10:52:54.968128+0800 GCDDemo[1926:297771] 当前线程:<NSThread: 0x6000023a23c0>{number = 3, name = (null)}
2018-09-21 10:52:54.976098+0800 GCDDemo[1926:297771] begin-- 主队列 + 同步执行
2018-09-21 10:52:54.978843+0800 GCDDemo[1926:297474] 1------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.978939+0800 GCDDemo[1926:297474] 1------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.979112+0800 GCDDemo[1926:297474] 2------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.979187+0800 GCDDemo[1926:297474] 2------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.979789+0800 GCDDemo[1926:297474] 3------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.979871+0800 GCDDemo[1926:297474] 3------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.979958+0800 GCDDemo[1926:297771] end-- 主队列 + 同步执行

从打印中可以总结如下:

  • 所有任务都是在主线程中执行的,并没有开启新的线程(所有放在主队列中的任务,都会放到主线程中执行),而且由于主队列串行队列,所以按顺序一个一个执行。
  • 所有任务都在打印的begin-- 和end-- 之间,这说明任务是添加到队列中马上执行的。

疑问点:为什么在其他线程调用不会崩溃卡住呢?
:因为syncMain任务放到了其他线程里,而syncMain方法中的几个任务都追加到主队列中,因为主队列现在没有正在执行的任务,所以会直接执行主队列的任务,一个个执行下去,所以这里不会卡住线程。

6.主队列 + 异步执行
特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务。

//主队列 + 异步执行
- (void)asyncMain
{
    NSLog(@"begin-- 主队列 + 异步执行");
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 主队列 + 异步执行");
}

输出的结果:
2018-09-21 11:13:55.685841+0800 GCDDemo[2099:354235] begin-- 主队列 + 异步执行
2018-09-21 11:13:55.686023+0800 GCDDemo[2099:354235] end-- 主队列 + 异步执行
2018-09-21 11:13:55.695339+0800 GCDDemo[2099:354235] 1------<NSThread: 0x600000c653c0>{number = 1, name = main}
2018-09-21 11:13:55.695555+0800 GCDDemo[2099:354235] 1------<NSThread: 0x600000c653c0>{number = 1, name = main}
2018-09-21 11:13:55.695661+0800 GCDDemo[2099:354235] 2------<NSThread: 0x600000c653c0>{number = 1, name = main}
2018-09-21 11:13:55.695733+0800 GCDDemo[2099:354235] 2------<NSThread: 0x600000c653c0>{number = 1, name = main}
2018-09-21 11:13:55.695795+0800 GCDDemo[2099:354235] 3------<NSThread: 0x600000c653c0>{number = 1, name = main}
2018-09-21 11:13:55.695852+0800 GCDDemo[2099:354235] 3------<NSThread: 0x600000c653c0>{number = 1, name = main}

从打印中可以总结如下:

1.所有任务都是在主线程中执行的,并没有开启新的线程,虽然异步执行具备开启线程的能力,但因为是主队列,所以所有任务都在主队列(主队列是串行队列)中,并且一个接一个的执行下去.
2.所有任务是在打印的begin-- 和end-- 之后才开始执行的,说明任务并不是马上执行,而是将所有任务添加到队列之后才开始同步执行。

  • GCD其他方法

1.栅栏方法(dispatch_barrier_async和dispatch_barrier_sync)

//dispatch_barrier_async
- (void) asyncbarrier
{
   dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
   dispatch_async(queue, ^{
       NSLog(@"----1-----%@", [NSThread currentThread]);
   });
   dispatch_async(queue, ^{
       NSLog(@"----2-----%@", [NSThread currentThread]);
   });
   dispatch_barrier_async(queue, ^{
       NSLog(@"----barrier-----%@", [NSThread currentThread]);
   });
   NSLog(@"aa, %@", [NSThread currentThread]);
   dispatch_async(queue, ^{
       NSLog(@"----3-----%@", [NSThread currentThread]);
   });
   NSLog(@"bb, %@", [NSThread currentThread]);
   dispatch_async(queue, ^{
       NSLog(@"----4-----%@", [NSThread currentThread]);
   });
}

输出的结果:
2018-09-21 14:36:35.946008+0800 GCDDemo[3665:625310] aa, <NSThread: 0x600002f013c0>{number = 1, name = main}
2018-09-21 14:36:35.946012+0800 GCDDemo[3665:625532] ----1-----<NSThread: 0x600002f57dc0>{number = 3, name = (null)}
2018-09-21 14:36:35.946027+0800 GCDDemo[3665:625531] ----2-----<NSThread: 0x600002f5d3c0>{number = 4, name = (null)}
2018-09-21 14:36:35.946139+0800 GCDDemo[3665:625310] bb, <NSThread: 0x600002f013c0>{number = 1, name = main}
2018-09-21 14:36:35.946161+0800 GCDDemo[3665:625531] ----barrier-----<NSThread: 0x600002f5d3c0>{number = 4, name = (null)}
2018-09-21 14:36:35.946258+0800 GCDDemo[3665:625532] ----4-----<NSThread: 0x600002f57dc0>{number = 3, name = (null)}
2018-09-21 14:36:35.946277+0800 GCDDemo[3665:625531] ----3-----<NSThread: 0x600002f5d3c0>{number = 4, name = (null)}

从打印中可以总结如下:

  • aa和bb都在主线程进行输出。
  • 先执行完barrier之前的任务,然后再执行自己的任务(barrier),最后执行barrier之后的任务(注意这里说的是任务不是下一行代码)。✨✨✨
  • 将自己的任务(barrier)插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务(3、4)插入到队列,然后执行任务。✨✨✨
//dispatch_barrier_sync
- (void) syncbarrier
{
   dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
   dispatch_async(queue, ^{
       NSLog(@"----1-----%@", [NSThread currentThread]);
   });
   dispatch_async(queue, ^{
       NSLog(@"----2-----%@", [NSThread currentThread]);
   });
   dispatch_barrier_sync(queue, ^{
       NSLog(@"----barrier-----%@", [NSThread currentThread]);
   });
   NSLog(@"aa, %@", [NSThread currentThread]);
   dispatch_async(queue, ^{
       NSLog(@"----3-----%@", [NSThread currentThread]);
   });
   NSLog(@"bb, %@", [NSThread currentThread]);
   dispatch_async(queue, ^{
       NSLog(@"----4-----%@", [NSThread currentThread]);
   });
}

输出的结果:
2018-09-21 14:38:45.374566+0800 GCDDemo[3689:634918] ----2-----<NSThread: 0x6000032dfa80>{number = 4, name = (null)}
2018-09-21 14:38:45.374567+0800 GCDDemo[3689:634917] ----1-----<NSThread: 0x6000032d04c0>{number = 3, name = (null)}
2018-09-21 14:38:45.374780+0800 GCDDemo[3689:634674] ----barrier-----<NSThread: 0x60000328d3c0>{number = 1, name = main}
2018-09-21 14:38:45.374858+0800 GCDDemo[3689:634674] aa, <NSThread: 0x60000328d3c0>{number = 1, name = main}
2018-09-21 14:38:45.374939+0800 GCDDemo[3689:634674] bb, <NSThread: 0x60000328d3c0>{number = 1, name = main}
2018-09-21 14:38:45.374956+0800 GCDDemo[3689:634917] ----3-----<NSThread: 0x6000032d04c0>{number = 3, name = (null)}
2018-09-21 14:38:45.375039+0800 GCDDemo[3689:634917] ----4-----<NSThread: 0x6000032d04c0>{number = 3, name = (null)}

从打印中可以总结如下:

  • aa和bb都在主线程进行输出。
  • barrier队列里面任务在主线程中执行。
  • 先执行完barrier之前的任务,然后再执行自己的任务(barrier),最后执行barrier之后的任务(注意这里说的是任务不是下一行代码)。✨✨✨
  • 需要等待自己的任务(barrier)结束之后,才会继续添加并执行写在barrier后面的任务(3、4),然后执行后面的任务。✨✨✨

注意点:在使用栅栏函数时,需使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用。

dispatch_barrier_asyncdispatch_barrier_sync异同点总结:

相同点:

  • 先执行完barrier之前的任务,然后再执行自己的任务(barrier),最后执行barrier之后的任务(注意这里说的是任务不是下一行代码)。

不同点:

  • dispatch_barrier_async将自己的任务(barrier)插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务(3、4)插入到队列,然后执行任务。
  • dispatch_barrier_sync需要等待自己的任务(barrier)结束之后,才会继续添加并执行写在barrier后面的任务(3、4),然后执行后面的任务。

2.延时执行方法(dispatch_after)

//dispatch_after
- (void)after {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"run-----%@",[NSThread currentThread]);
    });
}

输出的结果:
2018-09-25 14:06:01.709795+0800 GCDDemo[6349:555122] run-----<NSThread: 0x600002046940>{number = 1, name = main}

需要注意的地方:

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

3.快速迭代方法(dispatch_apply)

//dispatch_apply
- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"begin-- ");
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"end--");
}

输出的结果:
2018-09-25 14:23:48.028596+0800 GCDDemo[6495:604219] begin--
2018-09-25 14:23:48.028858+0800 GCDDemo[6495:604219] 0---<NSThread: 0x6000003613c0>{number = 1, name = main}
2018-09-25 14:23:48.028874+0800 GCDDemo[6495:604631] 1---<NSThread: 0x60000032f880>{number = 3, name = (null)}
2018-09-25 14:23:48.028894+0800 GCDDemo[6495:604632] 2---<NSThread: 0x6000003034c0>{number = 4, name = (null)}
2018-09-25 14:23:48.028917+0800 GCDDemo[6495:604630] 3---<NSThread: 0x600000303680>{number = 5, name = (null)}
2018-09-25 14:23:48.028944+0800 GCDDemo[6495:604219] 4---<NSThread: 0x6000003613c0>{number = 1, name = main}
2018-09-25 14:23:48.028961+0800 GCDDemo[6495:604631] 5---<NSThread: 0x60000032f880>{number = 3, name = (null)}
2018-09-25 14:23:48.029002+0800 GCDDemo[6495:604632] 6---<NSThread: 0x6000003034c0>{number = 4, name = (null)}
2018-09-25 14:23:48.029018+0800 GCDDemo[6495:604630] 7---<NSThread: 0x600000303680>{number = 5, name = (null)}
2018-09-25 14:23:48.029059+0800 GCDDemo[6495:604219] 8---<NSThread: 0x6000003613c0>{number = 1, name = main}
2018-09-25 14:23:48.029071+0800 GCDDemo[6495:604631] 9---<NSThread: 0x60000032f880>{number = 3, name = (null)}
2018-09-25 14:23:48.029626+0800 GCDDemo[6495:604219] end--

需要注意的地方:

  • 如果在 串行队列中使用dispatch_apply,那么就和 for 循环一样,按顺序同步执行,所以一般都是在异步队列中使用。
  • 因为是在并发队列中异步执行任务,各个任务的执行时间长短不定,所以最后结束顺序也不定(上面是顺序打印出来的,但是不一定就是顺序)。

4.只执行一次 ( dispatch_once)

//dispatch_once
- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行1次的代码(这里面默认是线程安全的)
    });
}

常用使用场景:单例

  • GCD队列组(dispatch_group

1.dispatch_group_notify
特点:监听 所有添加到group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。

 // dispatch_group_notify
- (void)notify {

   NSLog(@"group---begin");
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(3);
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(4);
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务1、任务2、任务3都执行完毕后,回到主线程执行下边任务
        NSLog(@"----4-----%@", [NSThread currentThread]);
        NSLog(@"group---end");
    });
    NSLog(@"----5-----end");
}

输出的结果:
2018-09-25 16:33:18.057450+0800 GCDDemo[7462:922204] group---begin
2018-09-25 16:33:18.057673+0800 GCDDemo[7462:922204] ----5-----end
2018-09-25 16:33:18.057726+0800 GCDDemo[7462:922320] ----1-----<NSThread: 0x60000196a540>{number = 3, name = (null)}
2018-09-25 16:33:21.062976+0800 GCDDemo[7462:922311] ----2-----<NSThread: 0x6000019073c0>{number = 4, name = (null)}
2018-09-25 16:33:22.058039+0800 GCDDemo[7462:922308] ----3-----<NSThread: 0x600001921140>{number = 5, name = (null)}
2018-09-25 16:33:22.058223+0800 GCDDemo[7462:922204] ----4-----<NSThread: 0x600001961280>{number = 1, name = main}
2018-09-25 16:33:22.058290+0800 GCDDemo[7462:922204] group---end

从打印中可以总结如下:

  • 当所有任务都执行完成之后,才执行dispatch_group_notify block 中的任务。
  • NSLog(@"----5-----end")会先执行,不会等到dispatch_group_notify都执行完了,才去执行。

2.dispatch_group_wait
特点:等待group关联的block执行完毕,才会继续往下执行。

 //dispatch_group_wait
- (void)wait {

    NSLog(@"group---begin");
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group---end");
}

输出的结果:
2018-09-26 10:24:51.163832+0800 GCDDemo[1907:215711] group---begin
2018-09-26 10:24:51.164102+0800 GCDDemo[1907:215790] ----2-----<NSThread: 0x600003fc0700>{number = 3, name = (null)}
2018-09-26 10:24:51.164118+0800 GCDDemo[1907:215793] ----3-----<NSThread: 0x600003f9b180>{number = 4, name = (null)}
2018-09-26 10:24:53.164214+0800 GCDDemo[1907:215791] ----1-----<NSThread: 0x600003fed880>{number = 5, name = (null)}
2018-09-26 10:24:53.164357+0800 GCDDemo[1907:215711] group---end

从打印中可以总结如下:

  • 执行完group关联的block之后,才继续往下执行的。
  • 打印顺序不是顺序的,说明是异步执行,没有等待(感觉是废话~)。

3.dispatch_group_enter和dispatch_group_leave

特点:``dispatch_group_enterdispatch_group_leave总是成对出现的。
dispatch_group_enter:用于添加对应任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数加1,当未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞和dispatch_group_notify的block执行。
dispatch_group_leave:用于减少任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数减1,dispatch_group_enterdispatch_group_leave要匹配,不然系统会认为group任务没有执行完毕。

//dispatch_group_enter、dispatch_group_leave
- (void)enterAndLeave{

    NSLog(@"group---begin");
    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-----%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
        NSLog(@"group---end");
    });

输出的结果:
2018-09-26 11:13:05.879685+0800 GCDDemo[2589:360180] group---begin
2018-09-26 11:13:05.880582+0800 GCDDemo[2589:360271] ----2-----<NSThread: 0x6000016580c0>{number = 4, name = (null)}
2018-09-26 11:13:05.880934+0800 GCDDemo[2589:360272] ----1-----<NSThread: 0x60000166a240>{number = 3, name = (null)}
2018-09-26 11:13:05.889769+0800 GCDDemo[2589:360180] ----3-----<NSThread: 0x600001634c00>{number = 1, name = main}
2018-09-26 11:13:05.889853+0800 GCDDemo[2589:360180] group---end
}
  • GCD信号量(dispatch_semaphore
    定义:信号量就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。其实,这有点类似锁机制了,只不过信号量都是系统帮助我们处理了,我们只需要在执行线程之前,设定一个信号量值,并且在使用时,加上信号量处理方法就行了。
    信号量主要有3个函数:
    1.dispatch_semaphore_create(信号量值):创建信号量,参数:信号量的初值,如果小于0则会返回NULL
    2.dispatch_semaphore_wait(信号量,等待时间):可以使总信号量减1。
    3.dispatch_semaphore_signal(信号量):发送一个信号,让信号总量加1。
    下面详细说一下:
    1.dispatch_semaphore_create函数的声明为:
    dispatch_samaphore_t dispatch_semaphore_create(long value);

传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。
值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL
2.dispatch_semaphore_signal函数的声明为:

     long dispatch_semaphore_signal(dispatch_semaphore_tdsema)

这个函数会使传入的信号量dsema的值加1
3. dispatch_semaphore_wait函数的声明为:

   long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

这个函数会使传入的信号量dsema的值减1
这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,不能直接传入整型float类型),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
4.dispatch_semaphore_signal和dispatch_semaphore_wait返回值代表的意义(这里要注意函数返回值和dsema的值的区别):
dispatch_semaphore_signal当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。
dispatch_semaphore_wait的返回值也为long类型。当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒,当其返回不为0时,表示timeout发生。
5.设置timeout
比较有用的两个宏:DISPATCH_TIME_NOWDISPATCH_TIME_FOREVER,当然你也可以自己创建一个dispatch_time_t类型的变量来使用。
例如:dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 1*1000*1000*1000);
6.关于信号量通俗的解释:
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。

-(void)dispatchSignal{
    //crate的value表示,最多几个资源可访问
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);   
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     

    dispatch_async(quene, ^{
        //任务1
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"----1-----");
        sleep(1);
        NSLog(@"complete ----1-----");
        dispatch_semaphore_signal(semaphore);       
    });
  
    dispatch_async(quene, ^{
         //任务2
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"----2-----");
        sleep(1);
        NSLog(@"complete ----2-----");
        dispatch_semaphore_signal(semaphore);       
    });
    
    dispatch_async(quene, ^{
         //任务3
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"----3-----");
        sleep(1);
        NSLog(@"complete ----3-----");
        dispatch_semaphore_signal(semaphore);       
    });   
}

信号量的初始值设置为1时输出结果:
2018-09-26 16:54:52.756489+0800 GCDDemo[7192:1007207] ----1-----
2018-09-26 16:54:53.759805+0800 GCDDemo[7192:1007207] complete ----1-----
2018-09-26 16:54:53.759963+0800 GCDDemo[7192:1007211] ----2-----
2018-09-26 16:54:54.763736+0800 GCDDemo[7192:1007211] complete ----2-----
2018-09-26 16:54:54.763988+0800 GCDDemo[7192:1007209] ----3-----
2018-09-26 16:54:55.769148+0800 GCDDemo[7192:1007209] complete ----3-----

信号量的初始值设置为2时输出结果:
2018-09-26 16:56:10.535546+0800 GCDDemo[7231:1012058] ----1-----
2018-09-26 16:56:10.535565+0800 GCDDemo[7231:1012060] ----2-----
2018-09-26 16:56:11.540570+0800 GCDDemo[7231:1012060] complete ----2-----
2018-09-26 16:56:11.540585+0800 GCDDemo[7231:1012058] complete ----1-----
2018-09-26 16:56:11.540705+0800 GCDDemo[7231:1012057] ----3-----
2018-09-26 16:56:12.541442+0800 GCDDemo[7231:1012057] complete ----3-----

信号量的初始值设置为3时输出结果:
2018-09-26 16:56:43.973919+0800 GCDDemo[7248:1014760] ----1-----
2018-09-26 16:56:43.973922+0800 GCDDemo[7248:1014763] ----2-----
2018-09-26 16:56:43.973923+0800 GCDDemo[7248:1014761] ----3-----
2018-09-26 16:56:44.974419+0800 GCDDemo[7248:1014763] complete ----2-----
2018-09-26 16:56:44.974419+0800 GCDDemo[7248:1014761] complete ----3-----
2018-09-26 16:56:44.974451+0800 GCDDemo[7248:1014760] complete ----1-----

从打印中可以总结如下:

  • 信号量的初始值设置的数量,就是最多能有几个线程运行的数量,当然了初始值为0,也可以使用dispatch_semaphore_signal使信号量加1

注意点: dispatch_semaphore_waitdispatch_semaphore_signal成对出现,否则程序会崩溃。

GCD的常见面试题

一.输出题(自行检测)

- (void)viewDidLoad {

    [super viewDidLoad];

       NSLog(@"1");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
    while (1) {
    };

}
- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_queue_create("come.test.serialQueue", DISPATCH_QUEUE_SERIAL);

       NSLog(@"1");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
    while (1) {
    };

}
- (void)viewDidLoad {

    [super viewDidLoad];

       NSLog(@"1");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");

}
- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_queue_create("come.test.serialQueue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");

}

二.解答题
1、 dispatch_group_async是否一定能实现线程同步?
2、dispatch_barrier_syncdispatch_barrier_async的区别和相同点有哪些?
3、dispatch_group_asyncdispatch_group_enter,dispatch_group_leave实现线程同步有什么区别?

实际使用中的一些例子

写在后面的话

有问题的地方,请多指教,这个主要供自己复习和学习使用,温故知新~

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

推荐阅读更多精彩内容

  • Grand Central Dispath(GCD):是iOS 4和OS X Snow Leopard 开始引入的...
    明若晴空阅读 1,153评论 0 0
  • 一:base.h 二:block.h 1. dispatch_block_flags:DISPATCH_BLOCK...
    小暖风阅读 2,404评论 0 0
  • 在iOS应用程序中,如果不做特殊的操作,程序的代码都是跑在主线程中的。在主线程中有一个RunLoop, iOS会在...
    勿与龙比阅读 762评论 0 9
  • 程序中同步和异步是什么意思?有什么区别? 解释一:异步调用是通过使用单独的线程执行的。原始线程启动异步调用,异步调...
    风继续吹0阅读 1,023评论 1 2
  • 我不得不承认,我用了五年多才完成了与前任的分离,从心里彻底地把他拿走了,再也不会每天想起。 前两天上知乎,一个知友...
    吴宁_16ee阅读 158评论 1 3