从源码分析swift GCD_DispatchGroup

前言:

最近在写需求的时候用到了DispatchGroup,一直没有深入去学习,既然遇到了那么就总结下吧。。。。

基本介绍:

任务组(DispatchGroup)

DispatchGroup 可以将多个任务组合在一起并且监听它们的完成状态。当所有任务都完成时,可以通过通知回调或等待的方式知道它们的执行结果。

Swift 复制代码
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的实现

Swift 复制代码
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()的源码:

Swift 复制代码
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其实是一个结构体,其代码如下:

Swift 复制代码
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)的代码:

Swift 复制代码
//_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()

其代码如下:

Swift 复制代码
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():

Swift 复制代码
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()

代码如下:

Swift 复制代码
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中,我们截出来研究:

Swift 复制代码
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的定义

Swift 复制代码
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

相关推荐
键盘敲没电7 小时前
【IOS】GCD学习
学习·ios·objective-c·xcode
SY.ZHOU7 小时前
Significant Location Change
macos·ios·cocoa
吴Wu涛涛涛涛涛Tao15 小时前
深入理解 Swift Codable:从基础到进阶
ios
大熊猫侯佩17 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)
数据库·swiftui·swift
大熊猫侯佩17 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)
数据库·swiftui·swift
大熊猫侯佩17 小时前
用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化
数据库·swiftui·swift
大熊猫侯佩17 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)
数据库·swiftui·swift
摘星编程20 小时前
抽象工厂模式深度解析:从原理到与应用实战
设计模式·抽象工厂模式·源码分析·软件架构·实战案例
Jouzzy20 小时前
【iOS安全】iPhone X iOS 16.7.11 (20H360) WinRa1n 越狱教程
安全·ios·iphone
二流小码农1 天前
鸿蒙开发:实现一个标题栏吸顶
android·ios·harmonyos