从源码分析swift GCD_DispatchGroup

前言:

        最近在写需求的时候用到了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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/939893.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线性代数基础与应用:基底 (Basis) 与现金流及单期贷款模型(中英双语)

具体请参考&#xff1a;https://web.stanford.edu/~boyd/vmls/ 下面的例子来源于这本书。 线性代数基础与应用&#xff1a;基底 (Basis) 与现金流及单期贷款模型 在线性代数中&#xff0c;基底&#xff08;Basis&#xff09;是一个重要的概念&#xff0c;广泛应用于信号处理、…

【python】OpenCV—Image Moments

文章目录 1、功能描述2、图像矩3、代码实现4、效果展示5、完整代码6、涉及到的库函数cv2.moments 7、参考 1、功能描述 计算图像的矩&#xff0c;以质心为例 2、图像矩 什么叫图像的矩&#xff0c;在数字图像处理中有什么作用&#xff1f; - 谢博琛的回答 - 知乎 https://ww…

【漏洞复现】CVE-2022-45206 CVE-2023-38905 SQL Injection

漏洞信息 NVD - CVE-2022-45206 Jeecg-boot v3.4.3 was discovered to contain a SQL injection vulnerability via the component /sys/duplicate/check. NVD - CVE-2023-38905 SQL injection vulnerability in Jeecg-boot v.3.5.0 and before allows a local attacker to…

现代风格VUE3易支付用户控制中心

适用系统 彩虹易支付 技术栈 vitevue3elementuiplusphp 亮点 独立前端代码,扩展开发,不改动系统文件,不影响原版升级 支持功能订制 界面预览

开发技术-Java改变图片格式

图片上传页未做控制&#xff0c;导致上传的是GIF格式&#xff0c;导致图片识别失败。需要将GIF格式转为JPEG格式。 代码&#xff0c;是找AI写的&#xff0c;记录一下&#xff1a; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; im…

【计算机视觉基础CV】03-深度学习图像分类实战:鲜花数据集加载与预处理详解

本文将深入介绍鲜花分类数据集的加载与处理方式&#xff0c;同时详细解释代码的每一步骤并给出更丰富的实践建议和拓展思路。以实用为导向&#xff0c;为读者提供从数据组织、预处理、加载到可视化展示的完整过程&#xff0c;并为后续模型训练打下基础。 前言 在计算机视觉的深…

Unity-Editor扩展GUI基本实现一个可拖拉放的格子列表

短短几百行代码,好吧,又是“参考”了国外的月亮 操作,还真地挺自然的。。。。。。国外的实现有点小牛 拖拉,增加+ 一个Element 鼠标左键长按,可以出提示 鼠标右键,清除Element, 有点小bug,不是很自然地完全清除, using System.Collections; using System.Collecti…

修改vscode中emmet中jsx和tsx语法中className的扩展符号从单引号到双引号 - HTML代码补全 - 单引号双引号

效果图 实现步骤 文件 > 首选项 > 设置搜索“”在settings.json中修改&#xff0c;增加 "emmet.syntaxProfiles": {"html": {"attr_quotes": "single"},"jsx": {"attr_quotes": "double","…

首批|云轴科技ZStack成为开放智算产业联盟首批会员单位

近日 &#xff0c;在Linux基金会AI & Data及中国开源软件推进联盟的指导之下&#xff0c;开放智算产业联盟成立大会在北京成功召开。在大会上&#xff0c;联盟首次公布了组织架构并颁发了首批会员单位证书。凭借ZStack AIOS平台智塔和在智算领域的技术创新&#xff0c;云轴…

HTN 78A3 6V~140V输入,3A实地异步降压变换器

1、特征 3A降压&#xff0c;内置250mΩ高侧功率管 输入电压范围:6V~140V 脉冲跳跃模式使得轻载下高效率 最高1MHZ可编程开关频率 COT纹波电压控制架构 欠压保护、过流保护和过热关断保护 无铅封装&#xff0c;ESOP8 2、应用 二轮电瓶车 太阳能系统 高压电池组 …

以太网帧、IP数据报图解

注&#xff1a;本文为 “以太网帧、IP数据报”图解相关文章合辑。 未整理去重。 以太网帧、IP数据报的图解格式&#xff08;包含相关例题讲解&#xff09; Rebecca.Yan已于 2023-05-27 14:13:19 修改 一、基础知识 UDP 段、IP 数据包&#xff0c;以太网帧图示 通信过程中&…

汽车IVI中控开发入门及进阶(三十九):ADAS的车道线检测

概述: 自动驾驶汽车中确保驾驶员和乘客安全环境的重要系统之一是高级驾驶员辅助系统(ADAS)。自适应巡航控制、自动制动/转向、车道保持系统、盲点辅助、车道偏离警告系统和车道检测都是ADAS的示例。车道检测向车辆的智能系统显示特定于车道线结构几何特征的信息,以显示车道…

Liveweb视频汇聚平台支持WebRTC协议赋能H.265视频流畅传输

随着科技的飞速发展和网络技术的不断革新&#xff0c;视频监控已经广泛应用于社会各个领域&#xff0c;成为现代安全管理的重要组成部分。在视频监控领域&#xff0c;视频编码技术的选择尤为重要&#xff0c;它不仅关系到视频的质量&#xff0c;还直接影响到视频的传输效率和兼…

搭建一个简单的Web服务器(Apache2.4)

一、下载安装包 Apache服务器的官方下载地址&#xff1a;http://httpd.apache.org windows系统的安装软件下载地址&#xff1a;https://www.apachelounge.com/download/ 下载的是免安装的版本&#xff1a;httpd-2.4.62-240904-win64-VS17.zip 解压后将 Apache24 拷贝到 D 盘&…

NTFS 文件搜索库

NTFS 文件搜索库 中文 | English 一个快速搜索NTFS卷文件的库 在这里插入图片描述 特性 快速扫描 NTFS 格式驱动器上的所有文件实时快速同步文件变更(创建, 更名, 删除)支持通配符查询文件名或文件路径重启自动更新文件变动, 无需重新进行全盘扫描 API描述 初始化并指定…

Ubuntu下C语言操作kafka示例

目录 安装kafka&#xff1a; 安装librdkafka consumer Producer 测试运行 安装kafka&#xff1a; Ubuntu下Kafka安装及使用_ubuntu安装kafka-CSDN博客 安装librdkafka github地址&#xff1a;GitHub - confluentinc/librdkafka: The Apache Kafka C/C library $ apt in…

JavaScript基础学习

1.一元运算符 正负号&#xff0c;自增运算符 2.比较运算符 判断相等用 字符串比较&#xff0c;比较的是ASC码 尽量不要比较小数&#xff0c;小数有精度 不同类型比较会有隐式转换 3.运算符优先级 4.分支语句 除了0&#xff0c;其余数字都是真。除了空字符串&#xff0c…

SQL进阶技巧:如何计算商品需求与到货队列表进出计划?

目录 0 需求描述 1 数据准备 2 问题分析 3 小结 累计到货数量计算 出货数量计算 剩余数量计算 0 需求描述 假设现有多种商品的订单需求表 DEMO_REQUIREMENT&#xff0c;以及商品的到货队列表 DEMO_ARR_QUEUE&#xff0c;要求按照业务需要&#xff0c;设计一个报表&#…

“调用函数”多出一个None

Python中函数缺省返回空值&#xff0c;试图打印没有return的函数都将是None。 (笔记模板由python脚本于2024年12月20日 19:59:03创建&#xff0c;本篇笔记适合会自定义python函数的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Fre…

Flask内存马学习

文章目录 参考文章环境搭建before_request方法构造内存马after_request方法构造内存马errorhandler方式构造内存马add_url_rule方式构造内存马 参考文章 https://www.mewo.cc/archives/10/ https://www.cnblogs.com/gxngxngxn/p/18181936 前人栽树, 后人乘凉 大佬们太nb了, …