前言:
最近在写需求的时候用到了DispatchGroup,一直没有深入去学习,既然遇到了那么就总结下吧。。。。
基本介绍:
任务组(DispatchGroup)
DispatchGroup 可以将多个任务组合在一起并且监听它们的完成状态。当所有任务都完成时,可以通过通知回调或等待的方式知道它们的执行结果。
let group = DispatchGroup()
let queue = DispatchQueue(label: ".com1",attributes: .concurrent)
queue.async(group: group) {
print("任务1完成")
}
queue.async(group: group) {
print("任务2完成")
}
// 当所有任务完成时通知
group.notify(queue: DispatchQueue.main) {
print("所有任务完成")
}
// 或者阻塞等待所有任务完成
group.wait()
print("所有任务完成(等待方式)")
输出:
任务2完成
任务1完成
所有任务完成(等待方式)
所有任务完成
正文:
Swift使用的GCD是桥接OC的源码。所以底层还是libdispatch。
可以去github上Apple官方仓库去下载:GitHub - swiftlang/swift-corelibs-libdispatch: The libdispatch Project, (a.k.a. Grand Central Dispatch), for concurrency on multicore hardware
下载源码后,可以在semaphore.c中找到DispatchGroup的实现。
create():
先来看看dispatch_group_create的实现
dispatch_group_create(void)
{
return _dispatch_group_create_with_count(0);
}
可以看到创建dispatch_group涉及到:_dispatch_group_create_with_count(long count)
那我们看下_dispatch_group_create_with_count()的源码:
DISPATCH_ALWAYS_INLINE
static inline dispatch_group_t
_dispatch_group_create_with_count(long count)
{
//dispatch_group_t就是dispatchGroup
//dispatch_group_t本质上就是dispatch_group_s 详见下方
dispatch_group_t dg = (dispatch_group_t)_dispatch_object_alloc(
DISPATCH_VTABLE(group), sizeof(struct dispatch_group_s));
//把count的值存进去结构体
_dispatch_semaphore_class_init(count, dg);
//如果有值 就执行os_atomic_store2o
if (count) {
os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); // <rdar://problem/22318411>
}
return dg;
}
我们一个一个来分析
通过搜索发现dispatch_group_t本质上就是dispatch_group_s
dispatch_group_s其实是一个结构体,其代码如下:
struct dispatch_group_s {
DISPATCH_SEMAPHORE_HEADER(group, dg);
//看名字知道和wait方法有关
int volatile dg_waiters;
//dispatch_continuation_s可以自行搜索 最后是个dispatch_object_s
//这里可以理解为存储一个链表的 链表头和尾。看参数名知道和notify有关
struct dispatch_continuation_s *volatile dg_notify_head;
struct dispatch_continuation_s *volatile dg_notify_tail;
};
creat()创建了一个dispatch_group_t(也是dispatch_group_s)出来,默认传进来的count是0,并且把count通过dispatch_semaphore_class_init(count, dg)存了起来。
我们再来看看dispatch_semaphore_class_init(count, dg)的代码:
//_dispatch_semaphore_class_init(count, dg);
static void
_dispatch_semaphore_class_init(long value, dispatch_semaphore_class_t dsemau)
{
//dsemau就是dg 本质就是把传递进来的count存起来
struct dispatch_semaphore_header_s *dsema = dsemau._dsema_hdr;
dsema->do_next = DISPATCH_OBJECT_LISTLESS;
dsema->do_targetq = _dispatch_get_root_queue(DISPATCH_QOS_DEFAULT, false);
//value就是传进来的count
dsema->dsema_value = value;
_dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
}
通过creat()方法我们知道我们创建了一个dispatch_group_s出来,并且把0存了起来。知道dispatch_group_s中有一个类似链表的头和尾,看参数名知道和notify有关。
enter():
enter() 本质上调用dispatch_group_enter()
其代码如下:
void
dispatch_group_enter(dispatch_group_t dg)
{
//os_atomic_inc_orig2o是宏定义,可以一直点进去看。本质上就是把dg的dg_value做+1操作。
long value = os_atomic_inc_orig2o(dg, dg_value, acquire);
if (slowpath((unsigned long)value >= (unsigned long)LONG_MAX)) {
DISPATCH_CLIENT_CRASH(value,
"Too many nested calls to dispatch_group_enter()");
}
if (value == 0) {
_dispatch_retain(dg); // <rdar://problem/22318411>
}
}
其实dispatch_group_enter()只是把dg的dg_value做一个+1的操作。如果dg_value值过大就会crash。如果dg_value为0就会释放
leave():
void
dispatch_group_leave(dispatch_group_t dg)
{
//dg_value -1
long value = os_atomic_dec2o(dg, dg_value, release);
if (slowpath(value == 0)) {
//当value==0 执行_dispatch_group_wake
return (void)_dispatch_group_wake(dg, true);
}
//不成对出现 crash
if (slowpath(value < 0)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_group_leave()");
}
}
与enter()相反,做减1操作。
从源码得知,leave的核心逻辑是判断value==0时候执行_dispatch_group_wake。同时当levae次数比enter多时候,value<0会crash
可以说DispatchGroup的核心逻辑就在_dispatch_group_wake方法中
_dispatch_group_wake()
代码如下:
DISPATCH_NOINLINE
static long
_dispatch_group_wake(dispatch_group_t dg, bool needs_release)
{
dispatch_continuation_t next, head, tail = NULL;
long rval;
// cannot use os_mpsc_capture_snapshot() because we can have concurrent
// _dispatch_group_wake() calls
//dispatch_group_s 中dg_notify_head
head = os_atomic_xchg2o(dg, dg_notify_head, NULL, relaxed);
if (head) {
// snapshot before anything is notified/woken <rdar://problem/8554546>
tail = os_atomic_xchg2o(dg, dg_notify_tail, NULL, release);
}
rval = (long)os_atomic_xchg2o(dg, dg_waiters, 0, relaxed);
if (rval) {
// wake group waiters
_dispatch_sema4_create(&dg->dg_sema, _DSEMA4_POLICY_FIFO);
_dispatch_sema4_signal(&dg->dg_sema, rval);
}
uint16_t refs = needs_release ? 1 : 0; // <rdar://problem/22318411>
if (head) {
// async group notify blocks
do {
next = os_mpsc_pop_snapshot_head(head, tail, do_next);
dispatch_queue_t dsn_queue = (dispatch_queue_t)head->dc_data;
//head就是notify的block 在目标队列dsn_queue上运行
_dispatch_continuation_async(dsn_queue, head);
_dispatch_release(dsn_queue);
} while ((head = next));
refs++;
}
if (refs) _dispatch_release_n(dg, refs);
return 0;
}
是否还记得前面提到的dispatch_group_s中的链表头和尾?
head = os_atomic_xchg2o(dg, dg_notify_head, NULL, relaxed);
_dispatch_group_wake的代码前半部分其实是:这里取出dispatch_group_s中的链表头,如果有链表头再取出链表尾。执行的真正逻辑在do_while中,我们截出来研究:
if (head) {
// async group notify blocks
do {
next = os_mpsc_pop_snapshot_head(head, tail, do_next);
dispatch_queue_t dsn_queue = (dispatch_queue_t)head->dc_data;
//head就是notify的block 在目标队列dsn_queue上运行
_dispatch_continuation_async(dsn_queue, head);
_dispatch_release(dsn_queue);
} while ((head = next));
refs++;
}
通过head->dc_data
拿到目标队列,然后通过_dispatch_continuation_async(dsn_queue, head)
将head运行在目标队列上。
那么head其实就是任务队列,这个队列中存储的是notify回调的block
这时候我们回头看dispatch_group_s的定义
struct dispatch_group_s {
DISPATCH_SEMAPHORE_HEADER(group, dg);
//看名字知道和wait方法有关
int volatile dg_waiters;
//这里就是把所有notify的回调block存进链表里,然后拿到头结点和尾结点。
struct dispatch_continuation_s *volatile dg_notify_head;
struct dispatch_continuation_s *volatile dg_notify_tail;
};
总结:
- DispatchGroup 在创建时候会建立一个链表,来存储notify的block回调。
- 判断notify执行的依据就是dg_value是否为0:当不调用enter和leave时候,dg_value=0,notify的回调会立即执行,并且有多个notify会按照顺序依次调用。
- 当有enter时候dg_value+1。leave时候-1。
- 当最后一个leave执行后,dg_value==0。去循环链表执行notify的回调
- 根据源码也得知,enter和leave必须成对出现:
当enter多的时候,dg_value永远大于0,notify不会被执行。
当leave多的时候,dg_value小于0,造成Crash
参考:
从源码分析Swift多线程—DispatchGroup | licc
一文看懂iOS多线程并发(NSThread、GCD、NSOperation&&NSOperationQueue)_ios nsstread nsoperation gcd-CSDN博客
GitHub - swiftlang/swift-corelibs-libdispatch: The libdispatch Project, (a.k.a. Grand Central Dispatch), for concurrency on multicore hardware