当前位置: 首页 > news >正文

从源码分析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_store2oif (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就是传进来的countdsema->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 -1long value = os_atomic_dec2o(dg, dg_value, release);if (slowpath(value == 0)) {//当value==0 执行_dispatch_group_wakereturn (void)_dispatch_group_wake(dg, true);}//不成对出现 crashif (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_headhead = 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 blocksdo {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 blocksdo {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

相关文章:

从源码分析swift GCD_DispatchGroup

前言&#xff1a; 最近在写需求的时候用到了DispatchGroup&#xff0c;一直没有深入去学习&#xff0c;既然遇到了那么就总结下吧。。。。 基本介绍&#xff1a; 任务组&#xff08;DispatchGroup&#xff09; DispatchGroup 可以将多个任务组合在一起并且监听它们的完成状态。…...

25计软新增考研院校!或可捡漏上岸!

C哥专业提供——计软考研院校选择分析专业课备考指南规划 新增的计算机与软件工程考研院校为考研同学带来了多方面的机遇&#xff0c;这些机遇不仅体现在过国家线后可能面临的更低竞争压力&#xff0c;还包括更多元化的教育选择和更广阔的就业前景&#xff1a; 一、降低竞争压…...

C# 线程安全集合

文章目录 引言一、ConcurrentBag<T>二、ConcurrentQueue<T>三、ConcurrentStack<T>四、ConcurrentDictionary<TKey, TValue>五、总结引言 在多线程编程环境中,多个线程可能同时访问和操作集合数据。如果使用普通集合,很容易引发数据不一致、错误结果…...

箱包发霉怎么处理 箱包发霉处理修复方法

箱包发霉怎么处理&#xff1f;箱包不仅是我们出行的必需品&#xff0c;更是承载着个人风格与品味的时尚配饰。然而箱包工厂生产的箱包&#xff0c;在潮湿多变的环境中&#xff0c;箱包很容易成为霉菌滋生的温床&#xff0c;尤其是那些长时间储存的箱包&#xff0c;更是霉菌的“…...

【每日学点鸿蒙知识】Charles抓包、lock文件处理、WebView组件、NFC相关、CallMethod失败等

1、HarmonyOS系统中如何使用Charles抓包&#xff1f; 在HarmonyOS操作系统中&#xff0c;使用Charles进行抓包的步骤如下&#xff1a; 在Charles中设置代理。 首先&#xff0c;在Charles的菜单栏上选择“Proxy”→“Proxy Settings”&#xff0c;然后填入代理端口&#xff0…...

【异常】GL-SFT1200路由器中继模式,TL-CPE1300D无法搜寻5G网问题分析

【异常】GL-SFT1200路由器中继模式,TL-CPE1300D无法搜寻5G网问题 情况实验结论情况 在用GL-SFT1200路由器切换中继模式时,由于web密码忘却,需要重置,但根据官网使用手册,或者对应的中文版手册,重置失败。通过跟商家联系,进行uboot刷机,提供了指导文档,尝试后刷机成功…...

LINUX--shell

函数 格式&#xff1a; func() { command } function 关键字可写&#xff0c;也可不写。 示例 1&#xff1a; #!/bin/bash func() { #定义函数 echo "Hello $1" } func world #执行主文件 # bash test.sh Hello world 数组 数组是相…...

TCP常见问题

文章目录 一、两种状态图二、常见问题1、MSL是什么 3、为何等待2MSL3、为何三次握手&#xff0c;不握手、握手一次、两次行吗4、为何四次挥手&#xff0c;三次行吗&#xff0c;两次行吗 一、两种状态图 四次挥手 二、常见问题 1、MSL是什么 MSL是Maximum Segment Lifetime的英…...

OpenCV学习——图像融合

import cv2 as cv import cv2 as cvbg cv.imread("test_images/background.jpg", cv.IMREAD_COLOR) fg cv.imread("test_images/forground.png", cv.IMREAD_COLOR)# 打印图片尺寸 print(bg.shape) print(fg.shape)resize_size (1200, 800)bg cv.resize…...

网速、续航双在线!2024随身WiFi品牌精选推荐!格行按键切三网值得买吗?

随身wifi这个东西大家用的还是蛮多的&#xff0c;特别是一些户外工作的人员&#xff0c;往往都需要配备一个随身wifi&#xff0c;这样户外工作的时候才有网络&#xff0c;工作才会比较方便一些。今天就来盘点2024年热门随身wifi哪个牌子的好用&#xff1f; 1.华为&#xff1a;通…...

ubuntu18.04连接不上网络问题

现象&#xff1a;右上角的网络图标消失&#xff0c;仅剩输入法、音量和开关图标&#xff0c;ifconfig只显示本地回环 原因&#xff1a;网络适配器未开启 解决&#xff1a; 1. 查看网络状态&#xff1a;cat /var/lib/NetworkManager/NetworkManager.state 这里显示是false&a…...

访谈积鼎科技总经理:国产CFD软件发展与未来趋势展望

傅彦国&#xff0c;上海积鼎信息科技有限公司创始人 记者&#xff1a;请傅总介绍下我国流体仿真行业的发展现状是怎样的&#xff1f; 傅彦国&#xff1a;自2018年政府加大了对核心技术自主研发的支持力度&#xff0c;国产CFD软件逐渐步入发展正轨。 首先&#xff0c;从市场规…...

【Linux知识】exec命令行详解

文章目录 概述主要用途和 bash 有什么区别&#xff1f; 概述 在 Linux 系统中&#xff0c;exec 命令用于执行一个可执行文件&#xff0c;替换当前进程的映像。也就是说&#xff0c;当你在终端中使用 exec 命令后&#xff0c;当前的 shell 会被替换为 exec 指定的程序&#xff…...

【学术小白的学习之路】基于情感词典的中文句子情感分析(代码词典获取在结尾)

【学术小白的学习之路】基于情感词典的情感分析 1.基础函数1.1 判断情感词的否定词数量1.2 导入情感词典1.3 切分句子1.3.1为什么划分1.3.2 划分代码 1.4 完整代码 2.导入词典3.中文情感分析算法思路4.1情感词获取思路4.2 计算情感分值4.3 得分的归一化处理 4.实证5.总结 本文的…...

Linux 中 grep、sed、awk 命令

1. awk&#xff1a;强大的文本分析工具 awk&#xff0c;全称“Awk语言”&#xff0c;是一种专门用于处理文本文件的语言。它不仅能够根据关键字匹配某一行&#xff0c;还能进行复杂的文本分析和处理。awk的语法简洁明了&#xff0c;功能强大&#xff0c;是文本处理领域的佼佼者…...

一起考高项啊--现代化基础设施(工业互联网)

1、工业互联网的内涵和外延 工业互联网不是互联网在工作的简单应用&#xff0c;是具有更为丰富的内涵和外延。 它既是工业数字化、网络化、智能化转型的基础设施&#xff0c;也是互联网、大数据、人工智能与实体经济深度融合的应用模式&#xff0c;同时也是一种新业态、新产业…...

python学opencv|读取图像(二十)使用cv2.circle()绘制圆形进阶

【1】引言 前序已经掌握了使用cv2.circle()绘制圆形的基本操作&#xff0c;相关链接为&#xff1a; python学opencv|读取图像&#xff08;二十&#xff09;使用cv2.circle()绘制圆形-CSDN博客 由于圆形本身绘制起来比较简单&#xff0c;因此可以自由操作的空间也就大&#x…...

期权懂|如何减小个股期权交易中的风险?

锦鲤三三每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 如何减小个股期权交易中的风险&#xff1f; 一、选择合适的期权合约 &#xff08;1&#xff09;选择活跃的期权合约&#xff1a;投资者应优先选择交易活跃的期权合约。交易活跃的…...

ubuntu20.04 wget下载--段错误 (核心已转储)

用wget下载时总是在快下载完成时遇到段错误 untu2004-9.1.0_1.0-1_am 99%[> ] 1.63G 3.11MB/s 剩余 1s s段错误 (核心已转储)有以下两种解决方法&#xff1a; 1.手动下载 wget后面就是要下载的资源的链接&#xff0c;直接复制到浏览器中下载即可 如&#xff1a…...

怎么样保持mysql和redis数据一致性

保持 MySQL 和 Redis 数据的一致性是一个常见的挑战,因为 MySQL 是传统的关系型数据库,而 Redis 是内存数据库,通常用于缓存和高性能存储。这两者的数据更新方式不同,特别是当 Redis 用作缓存时,可能会存在缓存和数据库之间的数据不一致问题。为了保持数据一致性,通常可以…...

剑指Offer|LCR 013. 二维区域和检索 - 矩阵不可变

LCR 013. 二维区域和检索 - 矩阵不可变 给定一个二维矩阵 matrix&#xff0c;以下类型的多个请求&#xff1a; 计算其子矩形范围内元素的总和&#xff0c;该子矩阵的左上角为 (row1, col1) &#xff0c;右下角为 (row2, col2) 。 实现 NumMatrix 类&#xff1a; NumMatrix(…...

aosp15 - Activity生命周期切换

本文探查的是&#xff0c;从App冷启动后到MainActivity生命周期切换的系统实现。 调试步骤 在com.android.server.wm.RootWindowContainer#attachApplication 方法下断点&#xff0c;为了attach目标进程在com.android.server.wm.ActivityTaskSupervisor#realStartActivityLock…...

vxe-table 虚拟滚动的动态响应

虚拟滚动主要是在有限范围内渲染想要显示的数据&#xff0c;主要体现在懒加载数据和动态渲染上。如何提高虚拟滚动的操作性呢&#xff1f;请看本章解析 1.什么是虚拟滚动&#xff1f;代码如何实现&#xff1f; VXE-Table提供了一种名为“虚拟滚动”的功能&#xff0c;该功能可…...

quasar dev 命令卡住很久

别以为这是一个瞬间的截图&#xff0c;其实停留在这里很久很久。 折腾挺久&#xff0c;无论npm run dev:proxy还是 quasar dev&#xff0c;都是一样的情况。 最终解决办法&#xff1a; 有语法问题&#xff0c;通过 quasar build 命令暴露出来错误所在的行数。...

黑盒RCE测试 异或测试

前言 了解了漏洞的原理之后就需要知道 他在哪能出现 并且被利用 这个还是很重要的 异或测试 使用异或&#xff08;XOR&#xff09;运算进行加密解密的原理_异或加密-CSDN博客 异或测试是在 白盒内执行的 一个例题看一下 输入什么都是会报错 这种情况就需要使用 异或计…...

kotlin中泛型中in和out的区别

概念含义 in关键字&#xff08;逆变&#xff09; 在Kotlin泛型中&#xff0c;in关键字主要用于定义逆变&#xff08;Contravariance&#xff09;。它表示一个泛型类型参数可以是指定类型或者它的超类型。简单来说&#xff0c;就是对于类型A和B&#xff0c;如果A是B的子类型&…...

c# iis 解决跨域问题

该错误是一个典型的跨域问题&#xff0c;说明从 http://www.fuc.com 发起的请求被目标服务器&#xff08;https://aip.baidubce.com&#xff09;拒绝&#xff0c;原因是目标服务器未返回正确的 AccessControlAllowOrigin 响应头。 解决方法 1. 了解问题的本质 CORS&#xff08…...

MySQL版本对应的mysql-connector-java版本下载地址

MySQL版本mysql-connector-java版本mysql-connector-java下载地址MySQL安装版下载地址MySQL免安装版下载地址5.1.x5.1.xmysql-connector-java 5.1.xMySQL Installer 5.1.xMySQL Community Server 5.1.x5.5.x5.1.x, 5.5.x mysql-connector-java 5.1.x, mysql-connector-java 5.5…...

【读书笔记】《论语别裁》爱与罪

一、内容摘要 《论语别裁》第01章讨论了孔子关于孝悌的思想&#xff0c;以及其在中国文化中的重要性和复杂性。文中引用了有子的观点&#xff0c;强调孝弟是为人之本。然而&#xff0c;随着历史的发展&#xff0c;孔子的思想也被误解或被用作维护专制统治的工具。通过司马迁的…...

uniApp上传文件踩坑日记

最近在做移动端app&#xff0c;开始接触uniapp。想着直接用PC端的前后端API去做文件上传&#xff0c;但是uniapp的底层把请求拆成了普通请求和文件上传请求&#xff0c;所以不能用一个axios去做所有请求的处理&#xff0c;拆成uni.request和uni.uploadFile去分别处理两种情况。…...