从源码分析swift GCD_DispatchGroup

news2024/12/23 2:20:23

前言:

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在C#中使用资源保存图像和文本和其他数据并在运行时加载

资源是您可以构建到应用程序中的图像、字符串、文本文件和其他数据。您的程序可以在运行时加载资源以显示新图片、文本或其他内容。 要将资源添加到项目&#xff0c;请打开“项目”菜单并选择最底部的“属性”命令。在“属性”页面上&#xff0c;单击“资源”选项卡。现在&…

基于MATLAB的图像增强

目录 一、背景及意义介绍背景图像采集过程中的局限性 意义 二、概述三、代码结构及说明&#xff08;一&#xff09;整体结构&#xff08;二&#xff09;亮度增强部分&#xff08;三&#xff09;对比度增强部分&#xff08;四&#xff09;锐度增强部分 四、复现步骤&#xff08;…

电脑丢失dll文件一键修复的多种方法分析,电脑故障修复攻略

电脑在使用过程中&#xff0c;有时会遇到DLL文件丢失的情况&#xff0c;这可能导致软件无法正常运行或系统出现故障。当面对这种状况时&#xff0c;不必过于慌张&#xff0c;因为有多种有效的修复方法可供选择。下面我们一起来看看电脑丢失dll文件的多种解决方法。 一.了解什么…

android EditText密码自动填充适配

android上的密码&#xff08;其实不仅仅是密码&#xff0c;可以是用户名也可以是邮箱&#xff09;自动填充&#xff0c;是需要考虑适配的。 官方文档&#xff1a;https://developer.android.com/identity/autofill/autofill-optimize?hlzh-cn 什么是自动填充 手机厂商一般会…

GO--堆(have TODO)

堆 堆&#xff08;Heap&#xff09;是一种特殊的数据结构。它是一棵完全二叉树&#xff08;完全二叉树是指除了最后一层外&#xff0c;每一层上的节点数都是满的&#xff0c;并且最后一层的节点都集中在左边&#xff09;&#xff0c;结放在数组&#xff08;切片&#xff09;中…

Yolov11学习笔记

YOLOv11学习笔记 1.conda环境变量配置 1.1pycharm中新建conda虚拟环境 在win11系统中&#xff0c;创建的conda虚拟环境不会在conda的安装目录下&#xff0c; 而是会在C:\Users\Admin.conda\envs 比如我的anaconda安装目录在F:\anaconda3 但是生成的虚拟环境在C:\Users\Adm…

算法 计算大的长方形容器中,存放一排小长形容器,计算出小长形容器中最后一个元素的x坐标的位置的实现方法

1、先上个图&#xff1a; 2、说明 1&#xff09;中间的蓝色长方形是里面的橙色长方形的容器&#xff0c;比如第一个图中width2width3&#xff0c;因为只有一个&#xff0c;第二个图中有二个小的长方形&#xff0c;也就是说width22width3&#xff0c;第三个图中有3个小长方形&a…

不同版本的 Redis 的键值对内存占用情况示例

不同版本的 Redis 的键值对内存占用情况示例 文章目录 不同版本的 Redis 的键值对内存占用情况示例Redis 6.0redisObjectdictEntrysds&#x1f340; 数据结构&#x1f340; sdslen() 函数&#x1f340; sdsReqType() 函数&#x1f340; sdsHdrSize() 函数 内存分配 - malloc() …

捕虫游戏-项目制作

前言 同学们前面已经学习了html css javascript基础部分了&#xff0c;为了巩固和熟练前面所学的知识&#xff0c;从今天起&#xff0c;我们要做一个捕虫游戏的项目。通过项目实战夯实基础&#xff0c;将所学的知识真正用到实战中&#xff0c;强化对网页设计的能力&#xff…

用docker快速安装电子白板Excalidraw绘制流程图

注&#xff1a;本文操作以debian12.8 最小化安装环境为host系统。 一、彻底卸载原有的残留 apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras 二、设置docker的安装源 # Add Dockers official G…

【从零开始入门unity游戏开发之——C#篇23】C#面向对象继承——`as`类型转化和`is`类型检查、向上转型和向下转型、里氏替换原则(LSP)

文章目录 一、as类型转化和is类型检查1、as 关键字使用场景&#xff1a;语法&#xff1a;示例&#xff1a;特点&#xff1a; 2、is 关键字使用场景&#xff1a;语法&#xff1a;示例&#xff1a;特点&#xff1a; 3、总结 二、向上转型和向下转型1、向上转型示例&#xff1a; 2…

Android GO 版本锁屏声音无效问题

问题描述 Android go版本 在设置中打开锁屏音开关&#xff0c;息屏灭屏还是无声音 排查 vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\keyguard\KeyguardViewMediator.java private void setupLocked() {...String soundPath Settings.G…

跟着问题学18——transformer模型详解及代码实战(3)Encode编码器

跟着问题学18——transformer模型详解及代码实战&#xff08;1&#xff09; 跟着问题学18——transformer详解(2)多头自注意力机制-CSDN博客 2.3 残差连接 通过自注意力层我们挖掘提取了原始数据的特征&#xff0c;但编码层中会有多个编码器&#xff0c;这会导致网络层数的加…

React系列(八)——React进阶知识点拓展

前言 在之前的学习中&#xff0c;我们已经知道了React组件的定义和使用&#xff0c;路由配置&#xff0c;组件通信等其他方法的React知识点&#xff0c;那么本篇文章将针对React的一些进阶知识点以及React16.8之后的一些新特性进行讲解。希望对各位有所帮助。 一、setState &am…

【原生js案例】移动端如何实现页面的入场和出场动画

好的css动画&#xff0c;能给用户体验带来很大的提升&#xff0c;同时也能增加app的趣味性&#xff0c;给人眼前一亮的感觉。那如何实现这种全屏的弹窗入场和退场的动画 实现效果 代码实现 UI样式美化 #musicDetails{width: 100%;height: 100%;top:0;left:0;position: absol…

Pyqt6在lineEdit中输入文件名称并创建或删除JSON文件

1、创建JSON文件 代码 import osdef addModulekeyWordFile(self):if "" ! self.lineEdit_module.text():moduleFile self.lineEdit_module.text() .jsonelse:self.toolLogPrinting(请输入模块名称)returnfilePath modulekeyWordFileDir moduleFileif os.path.e…

鸿蒙UI开发——组件滤镜效果

1、概 述 ArkUI为组件提供了滤镜效果设置&#xff0c;背景滤镜、前景滤镜、合成滤镜。我们可以通过以下方式为组件设置对应的滤镜效果。 Text(前景滤镜)// ..其他属性.foregroundFilter(filterTest1) // 通过 foregroundFilter 设置模糊效果Text(背景滤镜)// ...其他属性.bac…

均方误差损失函数(MSE)和交叉熵损失函数详解

为什么需要损失函数 前面的文章我们已经从模型角度介绍了损失函数&#xff0c;对于神经网络的训练&#xff0c;首先根据特征输入和初始的参数&#xff0c;前向传播计算出预测结果&#xff0c;然后与真实结果进行比较&#xff0c;得到它们之间的差值。 损失函数又可称为代价函…

抓包 127.0.0.1 (loopback) 使用 tcpdump+wireshark

直接使用 wireshark无法抓取 127.0.0.1环回的数据包&#xff0c;一种解决方法是先传到路由器再返回&#xff0c;但这样可能造成拥塞。 Linux 先使用tcpdump抓包并输出为二进制文件&#xff0c;然后wireshark打开。 比如 sudo tcpdump -i lo src host localhost and dst host…

免费GIS工具箱:轻松将glb文件转换成3DTiles文件

在GIS地理信息系统领域&#xff0c;GLB文件作为GLTF文件的二进制版本&#xff0c;主要用于3D模型数据的存储和展示。然而&#xff0c;GLB文件的使用频率相对较低&#xff0c;这是因为GIS系统主要处理的是地理空间数据&#xff0c;如地图、地形、地貌、植被、水系等&#xff0c;…