dispatch_group
概述
dispatch_group可以将GCD任务合并到一个组管理,也可以同时监听组内所有任务的执行情况。主要的API有以下几个,先看一下Dispatch Group的具体使用。
- dispatch_group_create
- dispatch_group_enter
- dispatch_group_leave
- dispatch_group_wait
- dispatch_group_notify
- dispatch_group_async
使用
1 | dispatch_group_t group = dispatch_group_create(); |
- dispatch_group_enter必须在dispatch_group_leave之前出现
- dispatch_group_enter和dispatch_group_leave必须成对出现
源码分析
dispatch_group_create
Dispatch Group的本质是一个初始value为LONG_MAX的semaphore,通过信号量来实现一组任务的管理,代码如下:
1 | dispatch_group_t dispatch_group_create(void) { |
dispatch_group_enter
1 | void dispatch_group_enter(dispatch_group_t dg) { |
dispatch_group_leave
1 | void dispatch_group_leave(dispatch_group_t dg) { |
dispatch_group_leave
的逻辑是将dispatch_group_t
转换成dispatch_semaphore_t
后将dsema_value
的值加一。
当value等于LONG_MAX时表示所有任务已完成,调用_dispatch_group_wake
唤醒group,因此dispatch_group_leave
与dispatch_group_enter
需成对出现。
- 如果
dispatch_group_enter
多调用时,造成valu不等于LONG_MAX,不会唤醒group,导致收不到semaphore_signal信号而卡住线程。 - 如果
dispatch_group_leave
多调用1次,dispatch_semaphore_t
的value会等于LONGMAX+1(2147483647+1),即long的负数最小值LONG_MIN(–2147483648)。此时value小于0,会出现”Unbalanced call to dispatch_group_leave()”的崩溃。
dispatch_group_wait
1 | long dispatch_group_wait(dispatch_group_t dg, dispatch_time_t timeout) { |
如果当前value的值为初始值,表示任务都已经完成,直接返回0,如果timeout为0的话返回超时。其余情况会调用_dispatch_group_wait_slow方法。
1 | static long _dispatch_group_wait_slow(dispatch_semaphore_t dsema, dispatch_time_t timeout) { |
与dispatch_semaphore的_dispatch_semaphore_wait_slow
方法类似,不同点点在于等待完之后调用的again函数会调用_dispatch_group_wake
唤醒当前group。_dispatch_group_wake
的分析见下面的内容。
dispatch_group_notify
1 | void dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq, |
dispatch_group_notify的具体实现在dispatch_group_notify_f函数里,逻辑就是将block和queue封装到dispatch_continuation_t里,并将它加到链表的尾部,如果链表为空同时还会设置链表的头部节点。如果dsema_value的值等于初始值,则调用_dispatch_group_wake执行唤醒逻辑。
dispatch_group_wake
1 | static long _dispatch_group_wake(dispatch_semaphore_t dsema) { |
dispatch_group_wake
首先会循环调用semaphore_signal
唤醒等待group的信号量,使dispatch_group_wait
函数中等待的线程得以唤醒;然后依次获取链表中的元素并调用dispatch_async_f
异步执行dispatch_group_notify
函数中注册的回调,使得notify中的block得以执行。
dispatch_group_async
dispatch_group_async
的原理和dispatch_async
比较类似,区别点在于group操作会带上DISPATCH_OBJ_GROUP_BIT
标志位。添加group任务时会先执行dispatch_group_enter
,然后在任务执行时会对带有该标记的执行dispatch_group_leave
操作。下面看下具体实现:
1 | void dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq, |
dispatch_group_async_f
与dispatch_async_f
代码类似,主要执行了以下操作:
- 调用
dispatch_group_enter
- 将block和queue等信息记录到
dispatch_continuation_t
中,并将它加入到group的链表中。 _dispatch_continuation_pop
执行时会判断任务是否为group,是的话执行完任务再调用dispatch_group_leave
以达到信号量value的平衡。_dispatch_continuation_pop
简化后的代码如下:
1 | static inline void _dispatch_continuation_pop(dispatch_object_t dou) { |
总结
dispatch_group
本质是个初始值为LONG_MAX的信号量,等待group中的任务完成其实是等待value恢复初始值。dispatch_group_enter
和dispatch_group_leave
必须成对出现。
如果dispatch_group_enter
比dispatch_group_leave
多一次,则wait函数等待的
线程不会被唤醒和注册notify的回调block不会执行;
如果dispatch_group_leave
比dispatch_group_enter
多一次,则会引起崩溃。
dispatch_group_async
添加group任务时会先执行dispatch_group_enter
,然后在任务执行时会对带有该标记的执行dispatch_group_leave
操作。