本文摘录自《Objective-C高级编程》一书,附加一些自己的理解,作为对GCD的总结。
此篇主要包含以下几个方面:
-
Dispatch Group
- dispatch_group_t
- dispatch_group_create()
- dispatch_group_async()
- dispatch_group_notify()
- dispatch_group_wait()
- dispatch_group_enter() / dispatch_group_leave()
- dispatch_barrier_async
Dispatch Group
当我们向队列中添加的多个任务全部执行完毕后想最后再执行一次“总结性”的任务,这种需求经常出现。虽然我们可以交给串行队列来处理,串行队列方便我们控制其执行顺序,但是串行队列不是多线程同时执行,效率低。即使可以达到这种效果,源代码也会变得颇为复杂。
在此种情况下使用Dispatch Group。例如下面源代码为:追加3个Block到Global Dispatch Queue,这些Block如果全部执行完毕,就会执行Main Dispatch Queue中结束处理用的Block。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"任务1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"任务2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"任务3");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
控制台:
因为向Global Dispatch Queue 即 Concurrent Dispatch Queue追加处理,多个线程并行执行,所以追加处理的执行顺序不定。执行时会发生变化,但是此执行结果的done一定是最后输出的。
无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可监视这些处理执行的结束。一旦检测到所有处理执行结束,就可将"总结性"的处理追加到Dispatch Queue中。这就是使用Dispatch Group的原因。
dispatch_group_async
函数与 dispatch_async
函数相同,都追加Block到指定的Dispatch Queue中。与 dispatch_async
函数不同的是指定生成的Dispatch Group为第一个参数。指定的Block 属于指定的Dispatch Group。
在追加到Dispatch Group中的处理全部执行结束时,该源代码中使用的dispatch_group_notify
函数会将执行的Block 追加到Dispatch Queue中,将第一个参数指定为要监视的Dispatch Group。在追加到该Dispatch Group的全部处理执行结束时,将第三个参数的Block追加到第二个参数的Dispatch Queue中。在dispatch_group_notify
函数中不管指定什么样的Dispatch Queue,属于Dispatch Group的全部处理在追加指定的Block时都已执行结束。
dispatch_group_wait
另外,在Dispatch Group 中也可以使用dispatch_group_wait
函数仅等待全部处理执行结束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"任务1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"任务2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"任务3");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"done");
});
控制台:
dispatch_group_wait
函数的第二个参数指定为等待的时间(超时)。它属于dispatch_ time_t
类型的值。该源代码使用DISPATCH_TIME_FOREVER
,意味着永久等待。只要属于Dispatch Group 的处理尚未执行结束,就会一直等待,中途不能取消。
如果 dispatch_group_wait
函数的返回值不为0,就意味着虽然经过了指定的时间,但属于Dispatch Group的某一个处理还在执行中。如果返回值为0,那么全部处理执行结束。当等待时间为DISPATCH_TIME_FOREVER
、由dispatch_group_wait
函数返回时,由于属于Dispatch Group的处理必定全部执行结束,因此返回值恒为0。
这里的“等待”是什么意思呢?这意味着一旦调用 dispatch_group_wait
函数,该函数就处于调用的状态而不返回。即执行dispatch_group_wait
函数的现在的线程(当前线程)停止。在经过 dispatch_group_wait
函数中指定的时间或属于指定Dispatch Group的处理全部执行结束之前,执行该函数的线程停止。
指定DISPATCH_TIME_NOW
,则不用任何等待即可判定属于Dispatch Group的处理是否执行结束。
在主线程的RunLoop的每次循环中,可检查执行是否结束,从而不耗费多余的等待时间,虽然这样也可以,但一般在这种情形下,还是推荐用 dispatch_group_notify
函数追加结束处理到Main Dispatch Queue中。这是因为 dispatch_ group_notify
函数可以简化源代码。
对比 dispatch_ group_notify
函数和 dispatch_group_wait
函数的不同
首先是 dispatch_ group_notify
函数
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
sleep(1);
NSLog(@"任务1");
});
dispatch_group_async(group, queue, ^{
sleep(1);
NSLog(@"任务2");
});
dispatch_group_async(group, queue, ^{
sleep(1);
NSLog(@"任务3");
});
dispatch_group_notify(group, queue, ^{
sleep(1);
NSLog(@"任务4");
});
NSLog(@"任务5");
控制台:
可以看出,任务5最先执行,也就是说
dispatch_group_notify
函数是非线程阻塞的。
接下来看看 dispatch_group_wait
函数
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"任务1");
sleep(1);
});
dispatch_group_async(group, queue, ^{
NSLog(@"任务2");
sleep(1);
});
dispatch_group_async(group, queue, ^{
NSLog(@"任务3");
sleep(1);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"任务5");
dispatch_group_async(group, queue, ^{
NSLog(@"done");
});
控制台:
可以看出,任务5是在
dispatch_group_wait
函数阻塞解除之后才执行,所以dispatch_group_wait
函数会阻塞线程。
总结:
dispatch_group_notify
函数不会阻塞线程,dispatch_group_wait
会阻塞线程dispatch_group_notify
函数可以直接添加任务,dispatch_group_wait
函数只是单纯的阻塞了线程继续向下执行,所以需要自己另行添加“总结性任务”-
dispatch_group_notify
函数中的参数queue
不论是否与dispatch_group_async
是相同的queue
队列,都会最后执行dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"任务4"); });
dispatch_group_enter / dispatch_group_leave
先看看苹果官方文档关于dispatch_group_enter
的描述:
Explicitly indicates that a block has entered the group.
显式的表示一个代码块已经加入到组中。
Calling this function increments the current count of outstanding tasks in the group. Using this function (with dispatch_group_leave) allows your application to properly manage the task reference count if it explicitly adds and removes tasks from the group by a means other than using the dispatch_group_async function. A call to this function must be balanced with a call to dispatch_group_leave. You can use this function to associate a block with more than one group at the same time.
调用此函数将增加组中未完成任务的当前计数。如果不使用dispatch_group_async
函数进行显式的增加或者删除组中的任务,使用此函数(配合dispatch_group_leave
函数)可让你的程序正确管理任务引用计数。此函数的调用必须与dispatch_group_leave
函数的调用相平衡。可以使用此函数将一个代码块与多个组同时关联。
再看看苹果官方文档关于dispatch_group_leave
的描述:
Explicitly indicates that a block in the group has completed.
显式的表示组中的一个代码块已经执行完毕。
Calling this function decrements the current count of outstanding tasks in the group. Using this function (with dispatch_group_enter) allows your application to properly manage the task reference count if it explicitly adds and removes tasks from the group by a means other than using the dispatch_group_async function.
调用此函数将减少组中未完成任务的当前计数。如果不使用dispatch_group_async
函数进行显式的增加或者删除组中的任务,使用此函数(配合dispatch_group_leave
函数)可允许你的程序正确管理任务引用计数。
A call to this function must balance a call to dispatch_group_enter. It is invalid to call it more times than dispatch_group_enter, which would result in a negative count.
此函数的调用必须与dispatch_group_enter
函数的调用相平衡。调用它的次数超过dispatch_group_enter
是无效的,这会导致负计数。
总的来说dispatch_group_enter
/ dispatch_group_leave
这两个函数在使用时是成对出现的。enter是表示要向组中添加任务(代码块),组中未完成任务计数 +1。leave表示组中有个任务完成了,组中未完成任务计数 -1。这两个方法核心功能便是操作组中的任务计数,而任务计数会影响到dispatch_group_wait
函数和dispatch_group_notify
函数。
下面看个例子:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"group task start");
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"任务1");
sleep(3);
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"任务全部结束!");
});
箭头指示处就是打印的时间秒数,dispatch_group_notify
中的任务的执行时间比“任务1”晚了3秒,可见dispatch_group_notify
将“任务1”看做了组中的任务。这就使得普通的异步函数也可以被加入到组中,方便做最后的“总结性”的处理。
dispatch_barrier_async
下面是头文件中对dispatch_barrier_async
函数的描述:
/*!
* @functiongroup Dispatch Barrier API
* The dispatch barrier API is a mechanism for submitting barrier blocks to a
* dispatch queue, analogous to the dispatch_async()/dispatch_sync() API.
* It enables the implementation of efficient reader/writer schemes.
* Barrier blocks only behave specially when submitted to queues created with
* the DISPATCH_QUEUE_CONCURRENT attribute; on such a queue, a barrier block
* will not run until all blocks submitted to the queue earlier have completed,
* and any blocks submitted to the queue after a barrier block will not run
* until the barrier block has completed.
* When submitted to a a global queue or to a queue not created with the
* DISPATCH_QUEUE_CONCURRENT attribute, barrier blocks behave identically to
* blocks submitted with the dispatch_async()/dispatch_sync() API.
*/
大概意思就是说:这一系列的API只对通过dispatch_queue_create()
创建出来的DISPATCH_QUEUE_CONCURRENT
并发队列有效。如果是其它的队列,比如全局队列(global queue)或者所有不是通过dispatch_queue_create()
创建出来的DISPATCH_QUEUE_CONCURRENT
并发队列,dispatch_barrier_async
/ dispatch_barrier_sync
就会变成跟dispatch_async
/ dispatch_sync
一样,失去其特殊性,也就是说dispatch_barrier_async
没有“栅栏”的作用了。
下面举一个例子来说明dispatch_barrier_async
的具体功能:
// 通过dispatch_queue_create创建出来的DISPATCH_QUEUE_CONCURRENT并发队列
dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);
__block int num = 10;
dispatch_async(queue, ^{
NSLog(@"读取任务1 num=%d",num);
});
dispatch_async(queue, ^{
NSLog(@"读取任务2 num=%d",num);
});
dispatch_async(queue, ^{
NSLog(@"读取任务3 num=%d",num);
});
dispatch_async(queue, ^{
NSLog(@"读取任务4 num=%d",num);
});
dispatch_async(queue, ^{
NSLog(@"读取任务5 num=%d",num);
});
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"第%d次写入", i+1);
}
num = 20;
});
dispatch_async(queue, ^{
NSLog(@"读取任务6 num=%d",num);
});
dispatch_async(queue, ^{
NSLog(@"读取任务7 num=%d",num);
});
dispatch_async(queue, ^{
NSLog(@"读取任务8 num=%d",num);
});
dispatch_async(queue, ^{
NSLog(@"读取任务9 num=%d",num);
});
dispatch_async(queue, ^{
NSLog(@"读取任务10 num=%d",num);
});
执行结果:
我们由此可以看出,在dispatch_barrier_async
之前添加到队列的读取任务1-5会并发执行,并且dispatch_barrier_async
添加到队列的写入任务会等到这些前面的读取任务1-5全部执行完才会执行。在执行dispatch_barrier_async
中的任务时,队列中其它所有任务都会停下来等待它,只有dispatch_barrier_async
中的任务全部完成时,后面的任务才会继续,而且依然是并发执行。
简单来说,在dispatch_barrier_async
之前添加进去的任务会并发执行,在dispatch_barrier_async
之后添加进去的也会并发执行,但是会被dispatch_barrier_async
像栅栏一样给隔开。在dispatch_barrier_async
中的任务执行时具有排他性,其它所有任务都得停下来等待它执行完毕。这就为读 / 写保护提供了良好的工具,在写入时没有人来竞争资源。
另外,dispatch_barrier_async
的函数名中有async,我们会猜到应该有另一个函数叫做dispatch_barrier_sync
,确实有这个函数,但是这个函数会造成线程阻塞,像dispatch_group_wait
一样。
dispatch_barrier_async
函数不会阻塞线程,所以主线程的任务不会受到阻碍
dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"读取任务1");
});
dispatch_barrier_async(queue, ^{
NSLog(@"写入");
});
NSLog(@"主线程的任务");
dispatch_async(queue, ^{
NSLog(@"读取任务2");
});
执行结果:
而dispatch_barrier_sync
函数会阻塞线程,所以主线程的任务会被阻挡到后面去
dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"读取任务1");
});
dispatch_barrier_sync(queue, ^{
NSLog(@"写入");
});
NSLog(@"主线程的任务");
dispatch_async(queue, ^{
NSLog(@"读取任务2");
});
执行结果: