从源码分析swift GCD_DispatchGroup

news/2024/12/20 19:44:46/

前言:

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


http://www.ppmy.cn/news/1556712.html

相关文章

samout llm解码 幻觉更低更稳定

这段代码定义了一个简单的对话生成系统&#xff0c;包括模型加载、词汇表加载、以及基于给定提示生成文本的功能。下面是对代码的解析&#xff1a; load_model_and_voc(device"cpu"): 该函数用于加载预训练的模型和词汇表&#xff08;vocabulary&#xff09;。它首先…

C++ Learning 友元•内部类

友元 在 C 中&#xff0c;友元是一个非常特殊的概念&#xff0c;它使得某些非成员函数或其他类能够访问类的私有成员和保护成员。通常&#xff0c;私有成员和保护成员只能在类的成员函数内部访问&#xff0c;但通过将函数或类声明为友元&#xff0c;能够绕过这个访问限制。 友元…

linux cpu 管理

视频教程&#xff1a;ubuntu cpu 管理_哔哩哔哩_bilibili 概述 平均负载&#xff0c;CPU 使用率&#xff0c;CPU上下文 1 平均负载 #查看命令&#xff1a; rootzyb:~# uptime 18:21:47 up 1:09, 2 users, load average: 0.00, 0.00, 0.00 依次则是过去 1 分钟、5 分钟、1…

在Visual Studio Code (VSCode) 中将终端输出重定向到一个文本文件中

在Visual Studio Code (VSCode) 中将终端输出重定向到一个文本文件中 在Visual Studio Code (VSCode) 中,你可以通过以下步骤将终端输出重定向到一个文本文件中: 1. 方法一 使用重定向符号 在终端中运行命令时,可以使用重定向符号 > 或 >> 将输出保存到文件中。 …

Android运行低版本项目可能遇到的问题

Android运行低版本项目可能遇到的问题 低版本项目总是遇到各种问题的&#xff0c;耐心点 一、gradle-xxx.xxx.xxx.zip一直下载不下来 在gradle-wrapper.properties可以试下 distributionBaseGRADLE_USER_HOME distributionPathwrapper/dists zipStoreBaseGRADLE_USER_HOME …

C/C++圣诞树

系列文章 序号直达链接1C/C爱心代码2C/C跳动的爱心3C/C李峋同款跳动的爱心代码4C/C满屏飘字表白代码5C/C大雪纷飞代码6C/C烟花代码7C/C黑客帝国同款字母雨8C/C樱花树代码9C/C奥特曼代码10C/C精美圣诞树11C/C俄罗斯方块12C/C贪吃蛇13C/C孤单又灿烂的神-鬼怪14C/C闪烁的爱心15C…

代码随想录刷题-数组

文章目录 1.二分查找1.答案2.思路3.扩展题目1.搜索插入位置1.答案2.思路 2.在排序数组中查找元素的第一个和最后一个位置1.答案2.思路 3.x 的平方根1.答案2.思路 4.有效的完全平方数1.答案2.思路 4.总结1.标准二分模板 2.移除元素1.答案2.思路3.扩展题目1.删除有序数组中的重复…

uniapp使用腾讯地图接口的时候提示此key每秒请求量已达到上限或者提示此key每日调用量已达到上限问题解决

要在创建的key上添加配额 点击配额之后进入分配页面&#xff0c;分配完之后刷新uniapp就可以调用成功了。