GCD中除了上篇文章提到的 dispatch_sync 和 dispatch_async 函数。还有一些虽不是很常用,但功能还是很强大的函数。跟着我一一来学习下吧。
1.dispatch_set_target_queue
dispatch_queue_create 函数生成的 Dispatch Queue 不管是 Serial Dispatch Queue 还是 Concurrent Didpatch Queue,执行优先级与默认优先级的 Global Didpatch Queue 相同。而变更生成的 Dispatch Queue 的执行优先级要使用 dispatch_set_target_queue 函数。当然iOS8之后,出了新的函数 dispatch_queue_attr_make_with_qos_class 也可以实现改变 Dispatch Queue 的执行优先级。两种方式如下。
iOS8之前,改变 Didpatch Queue 执行优先级代码如下
dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);
dispatch_queue_t golbalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(myQueue, golbalQueueBackground);
iOS8之后,改变 Didpatch Queue 执行优先级代码如下
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
dispatch_queue_tmyQueue =dispatch_queue_create("myQueue", attr);
这两种方式都可以获得一个优先级为 backgroud 的 Serial Queue。
dispatch_queue_create 函数除了可以改变 Didpatch Queue 的执行优先级,还有一些隐含功能。比如在多个 Serial Dispatch Queue 中用 dispatch_set_target_queue 函数指定目标为某一个 Serial Dispatch Queue,那么原先本应该并行执行的多个 Serial Dispatch Queue,在目标 Serial Dispatch Queue 上只能串行执行。Concurrent Dispatch Queue 用 dispatch_set_target_queue 函数指定目标为某一个 Serial Dispatch Queue,Concurrent Dispatch Queue 中的多个任务也是变成串行执行。
dispatch_queue_t serialQueue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue3 = dispatch_queue_create("queue3", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue4 = dispatch_queue_create("queue4", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue5 = dispatch_queue_create("queue5", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t golbalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(targetQueue, golbalQueueBackground);
dispatch_set_target_queue(serialQueue1, targetQueue);
dispatch_set_target_queue(concurrentQueue2, targetQueue);
dispatch_set_target_queue(serialQueue3, targetQueue);
dispatch_set_target_queue(serialQueue4, targetQueue);
dispatch_set_target_queue(serialQueue5, targetQueue);
dispatch_async(serialQueue1, ^{
NSLog(@"---任务1---%@", [NSThread currentThread]);
});
dispatch_async(concurrentQueue2, ^{
NSLog(@"---任务21---%@", [NSThread currentThread]);
});
dispatch_async(concurrentQueue2, ^{
NSLog(@"---任务22---%@", [NSThread currentThread]);
});
dispatch_async(concurrentQueue2, ^{
NSLog(@"---任务23---%@", [NSThread currentThread]);
});
dispatch_async(concurrentQueue2, ^{
NSLog(@"---任务24---%@", [NSThread currentThread]);
});
dispatch_async(concurrentQueue2, ^{
NSLog(@"---任务25---%@", [NSThread currentThread]);
});
dispatch_async(serialQueue3, ^{
NSLog(@"---任务3---%@", [NSThread currentThread]);
});
dispatch_async(serialQueue4, ^{
NSLog(@"---任务4---%@", [NSThread currentThread]);
});
dispatch_async(serialQueue5, ^{
NSLog(@"---任务5---%@", [NSThread currentThread]);
});
上面代码执行结果如下
2018-12-08 12:48:56.743282+0800 test[56851:22943615] ---任务1---<NSThread: 0x1d447b4c0>{number = 3, name = (null)}
2018-12-08 12:48:56.743378+0800 test[56851:22943615] ---任务21---<NSThread: 0x1d447b4c0>{number = 3, name = (null)}
2018-12-08 12:48:56.743409+0800 test[56851:22943615] ---任务22---<NSThread: 0x1d447b4c0>{number = 3, name = (null)}
2018-12-08 12:48:56.743429+0800 test[56851:22943615] ---任务23---<NSThread: 0x1d447b4c0>{number = 3, name = (null)}
2018-12-08 12:48:56.743445+0800 test[56851:22943615] ---任务24---<NSThread: 0x1d447b4c0>{number = 3, name = (null)}
2018-12-08 12:48:56.743463+0800 test[56851:22943615] ---任务25---<NSThread: 0x1d447b4c0>{number = 3, name = (null)}
2018-12-08 12:48:56.743480+0800 test[56851:22943615] ---任务3---<NSThread: 0x1d447b4c0>{number = 3, name = (null)}
2018-12-08 12:48:56.743496+0800 test[56851:22943615] ---任务4---<NSThread: 0x1d447b4c0>{number = 3, name = (null)}
2018-12-08 12:48:56.743513+0800 test[56851:22943615] ---任务5---<NSThread: 0x1d447b4c0>{number = 3, name = (null)}
2.Dispatch Group
在项目中会遇到这种需求,假设有四个任务,分别为任务A、任务B、任务C、任务D,其中任务D要等待任务A B C全部处理完了才可处理。如果 任务A B C都是串行执行,只需要使用一个 Serial Dispatch Queue,按照顺序把 任务 A B C依次放入队列中,最后追加任务D到队列中即可实现。但是如果 任务A B C不是串行执行的,而是并发执行的,该怎么实现这个功能呢。自己写代码用标志位来判断并发的任务A B C是否都执行完,再执行任务D也是可以实现的。但是比较复杂,这种情况下使用 Dispatch Group 就比较简单了。
参照代码如下
dispatch_queue_t golbalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, golbalQueue, ^{
NSLog(@"---任务A---%@", [NSThread currentThread]);
});
dispatch_group_async(group, golbalQueue, ^{
NSLog(@"---任务B---%@", [NSThread currentThread]);
});
dispatch_group_async(group, golbalQueue, ^{
NSLog(@"---任务C---%@", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"---任务D---%@", [NSThread currentThread]);
});
执行结果如下
2018-12-08 14:35:58.230566+0800 test[57002:22985593] ---任务A---<NSThread: 0x1d006e340>{number = 3, name = (null)}
2018-12-08 14:35:58.230700+0800 test[57002:22985588] ---任务C---<NSThread: 0x1d447a240>{number = 4, name = (null)}
2018-12-08 14:35:58.230746+0800 test[57002:22985593] ---任务B---<NSThread: 0x1d006e340>{number = 3, name = (null)}
2018-12-08 14:35:58.237903+0800 test[57002:22985488] ---任务D---<NSThread: 0x1d4069080>{number = 1, name = main}
因为向 Global Dispatch Queue 即 Concurent Dispatch Queue 追加处理,多个线程并行执行,所以任务A B C的执行顺序不定,但是任务D一定是最后执行的。
3.dispatch_barrier_async
在访问数据库和文件时,有读和写两种操作,如果使用 Serial Dispatch Queue 可避免数据竞争的问题。这样虽然数据竞争问题解决了,但这样做存在一个新的问题就是效率太低了。大家都知道,多个读操作并行执行是不存在数据竞争问题的,只是写入操作不能和其他写入操作以及包含读取操作的其他处理并行执行。
为了提高效率,可以这样做,读操作追加到 Concurrent Dispatch Queue 中,写操作在任一个读操作没有执行的状态下,追加到 Serial Dispatch Queue 中即可。这样虽可以实现,但是代码会很复杂。
为了更方便的解决上面问题,可以使用 dispatch_barrier_async 函数。假如现在有8个读操作,一个写操作,要求是先并行执行4个读操作,然后串行执行一个写操作,后面再并行执行后面4个读操作。
代码如下
dispatch_queue_t queue = dispatch_queue_create("barrier_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"---读操作1---");
});
dispatch_async(queue, ^{
NSLog(@"---读操作2---");
});
dispatch_async(queue, ^{
NSLog(@"---读操作3---");
});
dispatch_async(queue, ^{
NSLog(@"---读操作4---");
});
dispatch_barrier_async(queue, ^{
NSLog(@"------写操作1---");
});
dispatch_async(queue, ^{
NSLog(@"---读操作5---");
});
dispatch_async(queue, ^{
NSLog(@"---读操作6---");
});
dispatch_async(queue, ^{
NSLog(@"---读操作7---");
});
dispatch_async(queue, ^{
NSLog(@"---读操作8---");
});
执行结果如下
2018-12-08 15:27:08.999895+0800 test[57067:23007593] ---读操作3---
2018-12-08 15:27:08.999936+0800 test[57067:23007593] ---读操作4---
2018-12-08 15:27:08.999959+0800 test[57067:23007592] ---读操作2---
2018-12-08 15:27:08.999974+0800 test[57067:23007591] ---读操作1---
2018-12-08 15:27:09.000043+0800 test[57067:23007592] ------写操作1---
2018-12-08 15:27:09.000074+0800 test[57067:23007592] ---读操作5---
2018-12-08 15:27:09.000101+0800 test[57067:23007593] ---读操作8---
2018-12-08 15:27:09.000130+0800 test[57067:23007590] ---读操作7---
2018-12-08 15:27:09.000149+0800 test[57067:23007591] ---读操作6---
多次执行,读操作1 2 3 4执行顺序不一样,读操作5 6 7 8执行顺序也不一样,但是写操作一定是在读操作1 2 3 4执行完再执行,读操作5 6 7 8也一定是在写操作执行完成后才执行。
dispatch_barrier_async 函数会等待追加到 Concurrent Dispatch Queue 上的并行执行的处理全部结束之后,再将指定的处理追加到该 Concurrent Dispatch Queue 中,然后在由 dispatch_barrier_async 函数追加的处理执行完毕后,Concurrent Dispatch Queue 才恢复为一般的动作,追加到该 Concurrent Dispatch Queue 的处理又开始并行执行。
注意:dispatch_barrier_async方法有一个隐藏的坑。当并行队列是 dispatch_get_global_queue 时,dispatch_barrier_async 方法失效了。dispatch_barrier_async 只在 dispatch_queue_create 方法创建出来的并行队列中有效。
4.dispatch_apply
dispatch_apply 函数按指定的次数将指定的Block追加到指定的 Dispatch Queue 中,并等待全部处理执行结束。
代码如下
dispatch_queue_t golbalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, golbalQueue, ^(size_t index) {
NSLog(@"---%zu---%@", index, [NSThread currentThread]);
});
NSLog(@"---done---");
执行结果如下
2018-12-08 16:14:15.121169+0800 test[57130:23027202] ---0---<NSThread: 0x1d4076400>{number = 1, name = main}
2018-12-08 16:14:15.121195+0800 test[57130:23027285] ---2---<NSThread: 0x1d007fec0>{number = 4, name = (null)}
2018-12-08 16:14:15.121221+0800 test[57130:23027202] ---6---<NSThread: 0x1d4076400>{number = 1, name = main}
2018-12-08 16:14:15.121228+0800 test[57130:23027285] ---7---<NSThread: 0x1d007fec0>{number = 4, name = (null)}
2018-12-08 16:14:15.121246+0800 test[57130:23027202] ---8---<NSThread: 0x1d4076400>{number = 1, name = main}
2018-12-08 16:14:15.121263+0800 test[57130:23027285] ---9---<NSThread: 0x1d007fec0>{number = 4, name = (null)}
2018-12-08 16:14:15.121278+0800 test[57130:23027286] ---1---<NSThread: 0x1d4473340>{number = 3, name = (null)}
2018-12-08 16:14:15.121292+0800 test[57130:23027287] ---3---<NSThread: 0x1d4475800>{number = 6, name = (null)}
2018-12-08 16:14:15.121320+0800 test[57130:23027288] ---4---<NSThread: 0x1c8070c80>{number = 5, name = (null)}
2018-12-08 16:14:15.121348+0800 test[57130:23027302] ---5---<NSThread: 0x1c0070e40>{number = 7, name = (null)}
2018-12-08 16:14:15.121361+0800 test[57130:23027202] ---done---
因为是在 Global Dispatch Queue 中并行执行,多次执行,打印0-9顺序可能不一样,但是---done---一定是在最后打印的。因为 dispatch_apply 函数会等待全部处理执行结束。
函数第一个参数是重复次数,第二个参数是追加对象的 Dispatch Queue,第三个参数是追加的处理Block。跟以前不一样的是,第三个参数的Block是带有参数的Block,这是为了按第一个参数重复追加Block并区分各个Block而使用。
下面有一个利用 dispatch_apply 遍历 NSArray 的列子
NSArray *array = @[@"000", @"111", @"222", @"333", @"444", @"555", @"666", @"777", @"888", @"999"];
dispatch_queue_t golbalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(golbalQueue, ^{
dispatch_apply(array.count, golbalQueue, ^(size_t index) {
NSLog(@"---%zu---%@", index, [array objectAtIndex:index]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"---done---");
});
});
执行结果如下
2018-12-08 16:25:22.255896+0800 test[57134:23030709] ---2---222
2018-12-08 16:25:22.255885+0800 test[57134:23030708] ---0---000
2018-12-08 16:25:22.255884+0800 test[57134:23030571] ---1---111
2018-12-08 16:25:22.255939+0800 test[57134:23030708] ---3---333
2018-12-08 16:25:22.255941+0800 test[57134:23030571] ---4---444
2018-12-08 16:25:22.255950+0800 test[57134:23030708] ---5---555
2018-12-08 16:25:22.255954+0800 test[57134:23030571] ---6---666
2018-12-08 16:25:22.255961+0800 test[57134:23030708] ---7---777
2018-12-08 16:25:22.255964+0800 test[57134:23030571] ---8---888
2018-12-08 16:25:22.255971+0800 test[57134:23030708] ---9---999
2018-12-08 16:25:22.255999+0800 test[57134:23030571] ---done---
由于 dispatch_apply 函数与 dispatch_sync 函数相同,会等待处理执行结束,所以也要注意死锁问题。因此推荐在 dispatch_async 函数中非同步执行 dispatch_apply 函数。