[关闭]
@qidiandasheng 2016-09-03T15:14:17.000000Z 字数 15055 阅读 1954

多线程之GCD

iOS理论


介绍

GCD是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉它干什么就行。

任务和队列

在 GCD 中,加入了两个非常重要的概念: 任务 和 队列。

任务:即操作,你想要干什么,说白了就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。任务有两种执行方式: 同步执行 和 异步执行。

同步(sync) 和 异步(async) 的主要区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕!

如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。

如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。一般会开其他线程来运行任务。


队列:用于存放任务。一共有两种队列, 串行队列 和 并行队列。

放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。

并行队列中的任务根据同步或异步有不同的执行方式。

放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

同步方式 全局并发队列 手动创建的串行队列 主队列
同步(sync) 没有开启新的线程;串行执行任务 没有开启新的线程;串行执行任务 没有开启新的线程;串行执行任务
异步(async) 有开启新的线程;并行执行任务 有开启新的线程;串行执行任务 没有开启新的线程;串行执行任务

串行队列

主队列:这是一个特殊的串行队列。主队列用于刷新 UI,任何需要刷新 UI 的工作都要在主队列执行,而主队列里的任务都是在主线程执行的。所以这个串行队列比较特殊,它不管当前在什么线程,同步还是异步,主队列里的任务都会回到主线程执行。dispatch_get_main_queue()

如果是自己创建的串行队列同步时会在当前线程(一般会在主线程),异步时会在其他线程中执行。
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);

问题:
第一种:以下代码会造成死锁:

  1. NSLog(@"任务1 %@",[NSThread currentThread]);
  2. dispatch_sync(dispatch_get_main_queue(), ^{
  3. NSLog(@"任务2 %@",[NSThread currentThread]);
  4. });
  5. NSLog(@"任务3 %@",[NSThread currentThread]);
  6. 输出:
  7. 任务1 - <NSThread: 0x7fbac2c00bb0>{number = 1, name = main}

第二种:而以下代码为什么不会:

  1. dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
  2. NSLog(@"任务1 %@",[NSThread currentThread]);
  3. dispatch_sync(queue, ^{
  4. NSLog(@"任务2 %@",[NSThread currentThread]);
  5. });
  6. NSLog(@"任务3 %@",[NSThread currentThread]);
  7. 输出:
  8. 任务1 - <NSThread: 0x7fbac2c00bb0>{number = 1, name = main}
  9. 任务2 - <NSThread: 0x7fbac2c00bb0>{number = 1, name = main}
  10. 任务3 - <NSThread: 0x7fbac2c00bb0>{number = 1, name = main}

这两个都是串行队列。

我的理解是第一种的方法都是加在主队列中的,然后又用了同步。队列的顺序是【任务1->任务3->任务2】,而因为用了同步,任务2阻塞了当前的线程,当前线程要等任务2执行完才能执行任务3。所以这里造成了死锁。

而第二种是任务1和任务3都是在主队列上面,而任务2是在自己创建的队列上面,所以任务2不用等任务3先执行,也就不会造成死锁。同一个线程里可以有多个任务队列。

验证:

  1. dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
  2. NSLog(@"任务1 - %@", [NSThread currentThread]);
  3. dispatch_async(queue, ^{
  4. NSLog(@"任务2 - %@",[NSThread currentThread]);
  5. dispatch_sync(queue, ^{
  6. NSLog(@"任务3 - %@", [NSThread currentThread]);
  7. });
  8. NSLog(@"任务4 - %@",[NSThread currentThread]);
  9. });
  10. NSLog(@"任务5 - %@", [NSThread currentThread]);
  11. 输出:
  12. 任务1 - <NSThread: 0x7fbac2c00bb0>{number = 1, name = main}
  13. 任务5 - <NSThread: 0x7fbac2c00bb0>{number = 1, name = main}
  14. 任务2 - <NSThread: 0x7fbac2e0dff0>{number = 2, name = (null)}

任务2,任务3,任务4都是在自己创建的队列里,队列的顺序是【任务2->任务4->任务3】,而执行都是在另外一个GCD分配的异步线程里,任务3是同步任务会阻塞当前线程,所以任务4要等任务3执行完才能执行,所以会造成死锁。

如果将任务3放入主队列中,那就会正常输出了。而且任务3又回到了主线程执行。

  1. dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
  2. NSLog(@"任务1 - %@", [NSThread currentThread]);
  3. dispatch_async(queue, ^{
  4. NSLog(@"任务2 - %@",[NSThread currentThread]);
  5. dispatch_sync(dispatch_get_main_queue(), ^{
  6. NSLog(@"任务3 - %@", [NSThread currentThread]);
  7. });
  8. NSLog(@"任务4 - %@",[NSThread currentThread]);
  9. });
  10. NSLog(@"任务5 - %@", [NSThread currentThread]);
  11. 输出:
  12. 任务1 - <NSThread: 0x7fb119e04fb0>{number = 1, name = main}
  13. 任务5 - <NSThread: 0x7fb119e04fb0>{number = 1, name = main}
  14. 任务2 - <NSThread: 0x7fb119e85fe0>{number = 2, name = (null)}
  15. 任务3 - <NSThread: 0x7fb119e04fb0>{number = 1, name = main}
  16. 任务4 - <NSThread: 0x7fb119e85fe0>{number = 2, name = (null)}

并行队列

全局并行队列:这应该是唯一一个并行队列,只要是并行任务一般都加入到这个队列。dispatch_get_global_queue()

自己创建的:
dispatch_queue_t queue = dispatch_queue_create("com.demo.concurrentQueue", DISPATCH_QUEUE_CONCURRENT)

注意:并行队列不一定会创建多个线程,比如在同步情况下dispatch_sync,同一个线程里会有多个队列。

  1. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2. NSLog(@"任务1 - %@", [NSThread currentThread]);
  3. dispatch_async(queue, ^{
  4. NSLog(@"任务2 - %@",[NSThread currentThread]);
  5. dispatch_sync(queue, ^{
  6. NSLog(@"任务3 - %@", [NSThread currentThread]);
  7. });
  8. NSLog(@"任务4 - %@",[NSThread currentThread]);
  9. });
  10. NSLog(@"任务5 - %@", [NSThread currentThread]);
  11. 输出:
  12. 任务1 - <NSThread: 0x7fa9a9703820>{number = 1, name = main}
  13. 任务5 - <NSThread: 0x7fa9a9703820>{number = 1, name = main}
  14. 任务2 - <NSThread: 0x7fa9a971a1d0>{number = 2, name = (null)}
  15. 任务3 - <NSThread: 0x7fa9a971a1d0>{number = 2, name = (null)}
  16. 任务4 - <NSThread: 0x7fa9a971a1d0>{number = 2, name = (null)}

这个例子如果是串行队列里就会有造成死锁。而在这个全局并行队列里就不会,因为并行队列里的任务是同时执行的不用等待其他任务执行完才执行。


API

dispatch_queue_t

GCD的核心就是队列(dispatch_queue_t),然后把block里的任务放入队列中。

队列的生成有如下几种方式:

1:创建串行队列。

dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL);

2:创建并行队列。

dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT);

3:获得程序默认生成的并行队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

可设定优先级来选择高、中、低、更低三个优先级队列。DISPATCH_QUEUE_PRIORITY_HIGH(高)、DISPATCH_QUEUE_PRIORITY_DEFAULT(中)、DISPATCH_QUEUE_PRIORITY_LOW(低)、DISPATCH_QUEUE_PRIORITY_BACKGROUND(更低)。

4:获得主线程的dispatch串行队列

dispatch_queue_t queue = dispatch_get_main_queue();


dispatch_async和dispatch_sync

在这里我们一定要搞清楚异步同步并行串行的关系

打个比方吧,异步跟同步我们比作门。并行串行我们比作队伍。
那么我们两两组合会有四种情况(我们这里只比喻最简单的情况也就是两扇门跟两个队伍):
1: 异步并行。

开了两扇门,有两个队伍,那么表示同一时间可以有两个人进入房间,也就是同一时间可以执行两个任务。

2:异步串行。

我开了两扇门,但是只有一个队伍,你这个队伍里的人不能跑到另一扇门走,所以一次只能进一个,而且不能插队。也就是说同一时间只能执行一个任务。

3:同步并行

我只开了一扇门,但是有两个队伍,但是不是按每个队伍换着一个个进,而是按进入队伍的时间,也就是开始排队的时间(相当于你拿了一个号码牌),银行拿号懂不懂,其实不用排队也可以。也就是说同一时间也只能执行一个任务。

4:同步串行

只开了一扇门,一个队伍,那当然就一个个进了。也就是说同一时间也只能执行一个任务。

然后我们可以对照着下面这张表看看是不是正常的:

同步方式 全局并发队列 手动创建的串行队列 主队列
同步(sync) 没有开启新的线程;串行执行任务 没有开启新的线程;串行执行任务 没有开启新的线程;串行执行任务
异步(async) 有开启新的线程;并行执行任务 有开启新的线程;串行执行任务 没有开启新的线程;串行执行任务

dispatch_after

这个函数大家肯定很熟悉,就是延时执行block。

这种延迟执行的方式时间不是很精确。它其实是表示延时多少时间把block加入到队列中,但是如果这个队列中还有其他方法要执行,那么可能就会导致block延时执行的时间不精确了。
我们可以来举个栗子看看:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. NSLog(@"logout Now time");
  4. dispatch_async(dispatch_get_main_queue(), ^{
  5. sleep(5);
  6. NSLog(@"sleep 5s");
  7. });
  8. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  9. NSLog(@"after 3s,logout");
  10. });
  11. }

下面的输出,我们看到dispatch_after里的block并没有在3s后输出而是等待队列中的前一个block执行完了(5s后)才执行。

  1. 2016-09-03 18:30:35.275 test[54202:7968170] logout Now time
  2. 2016-09-03 18:30:40.282 test[54202:7968170] sleep 5s
  3. 2016-09-03 18:30:40.283 test[54202:7968170] after 3s,logout

这时你就想了我们可以不把block放入那个主队列啊,那我们来看看会怎么样:

把:

  1. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  2. NSLog(@"after 3s,logout");
  3. });

换成:

  1. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
  2. NSLog(@"after 3s,logout");
  3. });

我们看一下输出:

  1. 2016-09-03 18:38:30.078 test[54286:7974996] logout Now time
  2. 2016-09-03 18:38:33.079 test[54286:7975037] after 3s,logout
  3. 2016-09-03 18:38:35.087 test[54286:7974996] sleep 5s

是不是正确了,好像是正确了,但是我们实际写程序的过程中要记住一般我们使用dispatch_after的时候都是需要回到主线程中更新UI之类的,所以基本上block都是加入主队列的。
我这里只是给大家分析一下延时时间不精确的原因,希望大家发生问题了可以知道是什么原因造成的。


dispatch_once

dispatch_once也是我们在程序中常常用到的。主要就是用于单例模式,dispatch_once函数可以确保某个block在应用程序执行的过程中只被处理一次,而且它是线程安全的。

使用如下:

  1. + (instancetype)defaultInstance {
  2. static AFImageDownloader *sharedInstance = nil;
  3. static dispatch_once_t onceToken;
  4. dispatch_once(&onceToken, ^{
  5. sharedInstance = [[self alloc] init];
  6. });
  7. return sharedInstance;
  8. }

dispatch_barrier_async

这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。
barrier的意思是障碍物,也就是说他相当于一个障碍,要等他之前的任务全都完成它才会执行,它完成任务后,它之后的任务也才能执行。

还有个dispatch_barrier_sync,意思是跟上面差不多的,就是同步返回函数而已。

举个例子:

  1. dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
  2. dispatch_async(queue, ^{
  3. NSLog(@"A");
  4. });
  5. dispatch_async(queue, ^{
  6. NSLog(@"B");
  7. });
  8. //让barrier之前的线程执行完成之后才会执行barrier后面的操作
  9. dispatch_barrier_async(queue, ^{
  10. NSLog(@"拿到了AB的值");
  11. });
  12. dispatch_async(queue, ^{
  13. NSLog(@"C");
  14. });
  15. dispatch_async(queue, ^{
  16. NSLog(@"D");
  17. });

以上输出的顺序就是:(A,B)> 拿到了AB的值 > (C,D)。括号里的表示输出顺序不一定。
注意这里队列是并行队列。如果换成串行队列的话就是不使用dispatch_barrier_async整个的输出顺序也是按加入队列的顺序来的:A>B>拿到了AB的值>C>D。


dispatch_group_t

dispatch_group_t(派遣组)允许组织线程知道一个或者多个任务执行完成。下面简单的介绍几种用法:

1:dispatch_group_wait在派遣组中等待,阻止当前的线程继续执行:

  1. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2. dispatch_group_t group = dispatch_group_create();
  3. dispatch_group_async(group, queue, ^{
  4. NSLog(@"group 任务执行");
  5. });
  6. dispatch_group_async(group, queue, ^{
  7. NSLog(@"group 任务2执行");
  8. sleep(3);
  9. });
  10. //在派遣组中等待,阻止当前的线程继续执行
  11. dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
  12. NSLog(@"等待结束");

输出:

  1. 2016-09-03 20:54:53.772 test[55847:8061855] group 任务2执行
  2. 2016-09-03 20:54:53.772 test[55847:8061862] group 任务执行
  3. 2016-09-03 20:54:56.778 test[55847:8061816] 等待结束

2:dispatch_group_notify得到通知group中的任务都执行完毕,不会阻塞当前线程等待结果。

  1. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2. dispatch_group_t group = dispatch_group_create();
  3. dispatch_group_async(group, queue, ^{
  4. NSLog(@"group 任务执行");
  5. });
  6. dispatch_group_async(group, queue, ^{
  7. NSLog(@"group 任务2执行");
  8. sleep(3);
  9. });
  10. //不会阻塞当前线程,后面的可以继续执行
  11. dispatch_group_notify(group, queue, ^{
  12. NSLog(@"任务都执行完了");
  13. });
  14. NSLog(@"等待结束");

输出:

  1. 2016-09-03 21:03:39.917 test[55976:8068370] group 任务执行
  2. 2016-09-03 21:03:39.917 test[55976:8068319] 等待结束
  3. 2016-09-03 21:03:39.917 test[55976:8068362] group 任务2执行
  4. 2016-09-03 21:03:42.923 test[55976:8068362] 任务都执行完了

3:dispatch_group_enterdispatch_group_leave

表示进入group和退出group,每一个enter都要对应着一个leave,不然的话dispatch group永远不会结束。所以我们可以使用这个实现多个异步请求,在每个请求开始前dispatch_group_enter一次,然后再请求的回调里dispatch_group_leave一次,那么在所有请求结束的时候,dispatch_group_notify就可以得到通知了。

  1. dispatch_group_t group = dispatch_group_create();
  2. dispatch_group_enter(group);
  3. NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  4. NSLog(@"A");
  5. dispatch_group_leave(group);
  6. }] ;
  7. [task resume];
  8. dispatch_group_enter(group);
  9. NSURLSessionDataTask *task2 = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  10. NSLog(@"B");
  11. dispatch_group_leave(group);
  12. }] ;
  13. [task2 resume];
  14. dispatch_group_notify(group, dispatch_get_main_queue(), ^{
  15. NSLog(@"all end");
  16. });

输出为:(A,B) > all end 括号里的输出顺序不一定。

4:dispatch_semaphore

dispatch_semaphore表示信号量。是GCD中的一种同步的方式,与他相关的共有三个函数,分别是
dispatch_semaphore_createdispatch_semaphore_signaldispatch_semaphore_wait
具体的关于dispatch_semaphore的解释可以看这篇文章:关于dispatch_semaphore的使用

我这里利用使用3中的代码加入dispatch_semaphore实现同步:

  1. dispatch_group_t group = dispatch_group_create();
  2. dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  3. dispatch_group_enter(group);
  4. NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  5. NSLog(@"A");
  6. dispatch_group_leave(group);
  7. //信号量为0,则信号量值+1。则dispatch_semaphore_wait得到信号量大于0继续执行
  8. dispatch_semaphore_signal(semaphore);
  9. }] ;
  10. [task resume];
  11. //如果信号量为0,则会阻塞当前线程。等待信号量大于0,继续执行下面的语句,并信号量值-1。
  12. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  13. dispatch_group_enter(group);
  14. NSURLSessionDataTask *task2 = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  15. NSLog(@"B");
  16. dispatch_group_leave(group);
  17. dispatch_semaphore_signal(semaphore);
  18. }] ;
  19. [task2 resume];
  20. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  21. dispatch_group_notify(group, dispatch_get_main_queue(), ^{
  22. NSLog(@"all end");
  23. });

输出的顺序为: A > B > all end 此顺序是固定的。


dispatch_apply

重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回。第一个参数是迭代次数,第二个是所在的队列,第三个是当前索引,dispatch_apply可以利用多核的优势,所以输出的index顺序不是一定的:

  1. dispatch_queue_t queue=dispatch_get_global_queue(0, 0);
  2. dispatch_apply(10, queue, ^(size_t index) {
  3. NSLog(@"%zu",index);
  4. });
  5. NSLog(@"done");

输出结果(index顺序是随机的,done最后执行):

  1. 2016-09-03 19:56:54.779 test[54856:8003496] 1
  2. 2016-09-03 19:56:54.779 test[54856:8003608] 3
  3. 2016-09-03 19:56:54.779 test[54856:8003540] 2
  4. 2016-09-03 19:56:54.779 test[54856:8003496] 4
  5. 2016-09-03 19:56:54.779 test[54856:8003533] 0
  6. 2016-09-03 19:56:54.780 test[54856:8003496] 7
  7. 2016-09-03 19:56:54.780 test[54856:8003540] 6
  8. 2016-09-03 19:56:54.780 test[54856:8003496] 9
  9. 2016-09-03 19:56:54.780 test[54856:8003608] 5
  10. 2016-09-03 19:56:54.780 test[54856:8003533] 8
  11. 2016-09-03 19:56:54.780 test[54856:8003496] done

dispatch_suspend和dispatch_resume

dispatch_suspend:暂停队列
dispatch_resume: 恢复队列

这些函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但还没有执行的任务不会执行,直到队列被恢复。

如果你只有暂停队列而没有恢复队列的话,程序是会崩溃的。所以说这两个是要一起用的。

使用如下:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. NSLog(@"开始时间");
  4. dispatch_queue_t queue = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
  5. dispatch_async(queue, ^{
  6. NSLog(@"队列被执行");
  7. });
  8. dispatch_suspend(queue);
  9. sleep(10);
  10. dispatch_resume(queue);
  11. }

输出:

  1. 2016-09-03 20:20:55.817 test[55245:8025135] 开始时间
  2. 2016-09-03 20:21:05.823 test[55245:8025380] 队列被执行

dispatch_set_target_queue

它会把需要执行的任务对象指定到不同的队列中去处理。比如把queue1指定到queue2中去。那么相当于queue1是依赖于queue2
dispatch_set_target_queue(queue1, queue2);

那么我们把任务分别加入queue1queue2中,然后异步执行,本来的话是这两个任务是同时执行的。如下所示:

  1. dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
  2. dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
  3. dispatch_async(targetQueue, ^{
  4. NSLog(@"target in");
  5. [NSThread sleepForTimeInterval:3.f];
  6. NSLog(@"target out");
  7. });
  8. dispatch_async(queue1, ^{
  9. NSLog(@"1 in");
  10. [NSThread sleepForTimeInterval:1.f];
  11. NSLog(@"1 out");
  12. });

输出如下(1 in和target in是随机的):

  1. 2016-09-03 20:36:29.028 test[55546:8043854] 1 in
  2. 2016-09-03 20:36:29.028 test[55546:8043739] target in
  3. 2016-09-03 20:36:30.029 test[55546:8043854] 1 out
  4. 2016-09-03 20:36:32.034 test[55546:8043739] target out

那我们现在利用dispatch_set_target_queuequeue1加入到targetQueue中。

  1. dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
  2. dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
  3. dispatch_set_target_queue(queue1, targetQueue);

输出如下,这个顺序肯定是固定的,因为使用了dispatch_set_target_queue相当于他们就是同一个串行队列了,他们的执行顺序也是按照FIFO(先进先出)来执行的:

  1. 2016-09-03 20:39:50.503 test[55589:8046629] target in
  2. 2016-09-03 20:39:53.504 test[55589:8046629] target out
  3. 2016-09-03 20:39:53.505 test[55589:8046629] 1 in
  4. 2016-09-03 20:39:54.510 test[55589:8046629] 1 out

dispatch source

dispatch源可以类比于runloop源。当dispatch源产生数据或发生改变时,会自动在dispatch指定的队列上运行相应地block。

dispatch源的使用基本为以下步骤:

1:创建dispatch源

这里使用加法来合并dispatch源数据,最后一个参数是指定dispatch队列。
dispatch_source_t source = dispatch_source_create(dispatch_source_type, handler, mask, dispatch_queue);

2:设置响应dispatch源事件的block

  1. //设置响应dispatch源事件的block,在dispatch源指定的队列上运行
  2. dispatch_source_set_event_handler(source, ^{
  3. //得到dispatch源数据
  4.   long value = dispatch_source_get_data(source)
  5.   
  6.   //合并dispatch源数据
  7.   dispatch_source_merge_data(source, value);
  8. });

3:启动dispatch源

dispatch源创建后处于suspend状态,所以需要启动dispatch源
dispatch_resume(source);

简单的使用:

  1. NSRunningApplication *mail = [NSRunningApplication
  2. runningApplicationsWithBundleIdentifier:@"com.apple.mail"];
  3. if (mail == nil) {
  4. return;
  5. }
  6. pid_t const pid = mail.processIdentifier;
  7. self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid,
  8. DISPATCH_PROC_EXIT, DISPATCH_TARGET_QUEUE_DEFAULT);
  9. dispatch_source_set_event_handler(self.source, ^(){
  10. NSLog(@"Mail quit.");
  11. });
  12. dispatch_resume(self.source);

常见问题及解决方法

dispatch_sync死锁问题研究

当程序进入死锁状态的时候我们点击调试进入调试状态。能看到左边debug navigator里看堆栈回溯信息。看到如下图所示状态。

这里程序执行了_dispatch_barrier_sync_f_slow函数体,在_dispatch_barrier_sync_f_slow中,使用_dispatch_queue_push将我们的block压入main queue的FIFO队列中,然后等待信号量,ready后被唤醒。

然后dispatch_semaphore_wait返回_dispatch_semaphore_wait_slow(dsema, timeout)函数,持续轮训并等待,直到条件满足。

所以在此过程中,我们最初调用的dispatch_sync函数一直得不到返回,main queue被阻塞,而我们的block又需要等待main queue来执行它。死锁愉快的产生了。

DSCrashDemo里有写关于死锁的例子。


如何判断多个异步请求完成

有a、b、c、d 4个异步请求,如何判断a、b、c、d都完成执行?如果需要a、b、c、d顺序执行,该如何实现?

利用GCD中的dispatch_barrier_asyncdispatch_group_t都可以实现。
实现例子

参考

底层并发 API

GCD学习之——Dispatch I/O

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注