前言
书接上回函数与队列,我们根据GCD
底层源码分析,知道了队列分为串行
和并发
两种类型,有两种常用的队列:主队列
和全局队列
,其中主队列类型是串行
,而全局队列类型是并发
。
队列的底层创建流程大致分析完了,今天我们再重点看看函数的底层执行流程
,就是GCD的Block的初始化,以及被调用的过程。我们以异步并发
为例看看👇
dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conque, ^{
NSLog(@"12334");
});
dispatch_async底层
#ifdef __BLOCKS__
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME;
dispatch_qos_t qos;
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
#endif
- 入参work就是block,需要执行的任务;
- 局部变量
dc
dc_flag
都是为了初始化qos
-->_dispatch_continuation_init(dc, dq, work, 0, dc_flags);
- 初始化完成后,接着
_dispatch_continuation_async
dispatch_async的源码不多,就这么几行,但是我们知道,异步并发任务是会开启子线程的,在子线程去执行block任务,所以,我们需要关注2个点:
- 子线程创建的时机点
- 任务block执行的时机点
1 子线程的创建流程
我们先进入_dispatch_continuation_init
的流程,看看是否有子线程创建?
1.1 _dispatch_continuation_init 底层流程
接着来到_dispatch_continuation_init_f
,其中release的func是第4个入参,而work就是需要执行的任务是ctxt第3个入参,继续看看_dispatch_continuation_init_f
- _dispatch_continuation_init_f
- _dispatch_continuation_voucher_set
-
_dispatch_continuation_priority_set
--> dc的dc_priority的设置 👇
以上并没有发现关于子线程创建的代码,接下来我们去_dispatch_continuation_async
找找。
1.2 _dispatch_continuation_async底层流程
根据返回值,锁定到了dx_push
。全局搜索dx_push
👇
继续看dq_push
👇
如果是
并发队列
,那么.dq_push = _dispatch_lane_concurrent_push
,接着来到_dispatch_lane_concurrent_push
👇
显然是非栅栏函数,那么进入_dispatch_continuation_redirect_push
👇
那do_targetq
是什么呢?得回到队列的创建dispatch_queue_create
去查看👇
那么,_dispatch_continuation_redirect_push
里的dx_push
时的队列是_dispatch_get_root_queue()
👇
同理,找dispatch_queue_global_t
对应的 dq_push
的方法
至此,我们知道了
dispatch_async
中子线程创建
的调用链👇(层级比较深)
dispatch_async
-->_dispatch_continuation_async
-->dx_push
-->dq_push
--> 并发队列:_dispatch_lane_concurrent_push
-->_dispatch_continuation_redirect_push
_dispatch_continuation_redirect_push
-->dx_push
(此时是global_queue
) -->_dispatch_root_queue_push
-->_dispatch_root_queue_push_inline
-->_dispatch_root_queue_poke
-->_dispatch_root_queue_poke_slow
-->线程池调度,创建线程pthread_create
2 任务Block的调用流程
那么接下来的问题就是 block何时调用?
再看哪里调用的block --> 类似这样的代码block(xxx)
? 我们可以在dispatch_async的任务block中打断点,然后bt查看调用栈👇
注意到在调用栈中有一个_dispatch_worker_thread2
,现在重点就来到 --> 什么时候调起的_dispatch_worker_thread2
?
我们先全局搜索一下_dispatch_worker_thread2
👇
发现全在一个方法里面 --> _dispatch_root_queues_init_once
中,同理,全局搜索👇
再全局搜索_dispatch_root_queues_init
👇
_dispatch_root_queue_poke_slow
是否很熟悉? 就是我们上面在查找创建子线程
时调用栈走过的方法,那么此时任务block的调用和子线程的创建产生了联系
,这个联系
就是_dispatch_root_queue_poke_slow
。
2.1 _dispatch_root_queues_init
现在我们重点来看看_dispatch_root_queues_init
,是否真的调用任务block?
DISPATCH_STATIC_GLOBAL(dispatch_once_t _dispatch_root_queues_pred);
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_root_queues_init(void)
{
dispatch_once_f(&_dispatch_root_queues_pred, NULL,
_dispatch_root_queues_init_once);
}
dispatch_once_f
是否有些熟悉?莫非是单例
?我们平时写的单例是这样的👇
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// input your code
});
搜索一下dispatch_once
源码👇
果然, dispatch_once_f
是个单例方法。
2.2 单例的底层
dispatch_once_f
源码👇
DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
dispatch_once_gate_t l = (dispatch_once_gate_t)val;
#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
if (likely(v == DLOCK_ONCE_DONE)) {
return;
}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
if (likely(DISPATCH_ONCE_IS_GEN(v))) {
return _dispatch_once_mark_done_if_quiesced(l, v);
}
#endif
#endif
if (_dispatch_once_gate_tryenter(l)) {
return _dispatch_once_callout(l, ctxt, func);
}
return _dispatch_once_wait(l);
}
总之,这个_dispatch_once_gate_tryenter
判断条件,就能保证当前只有一个线程进去执行代码,那为什么只能执行一次呢?还是看_dispatch_once_gate_broadcast
里的 _dispatch_once_mark_done
2.3 回到Block的调用时机
即什么时候调起的_dispatch_worker_thread2
?
在_dispatch_root_queues_init
时,单例执行的任务block是_dispatch_root_queues_init_once
👇
再来看看
_dispatch_root_queues_init_once
👇
上图可知,在_dispatch_root_queues_init_once
中完成了线程与任务_dispatch_worker_thread2的绑定过程
。接下来就看看_dispatch_worker_thread2
的大致流程。
2.4 _dispatch_worker_thread2底层
最终,我们来到了dx_invoke
和_dispatch_continuation_invoke_inline
。
-
_dispatch_continuation_invoke_inline
- dx_invoke
和dx_push
道理一样,是个宏定义👇
#define dx_invoke(x, y, z) dx_vtable(x)->do_invoke(x, y, z)
都是从dx_vtable
中的属性而来。再搜索do_invoke
👇
因为是并发队列queue_concurrent,对应的invoke方法名是_dispatch_lane_invoke
👇
invoke是入参_dispatch_queue_class_invoke_handler_t
类型,也就是方法_dispatch_lane_invoke2
👇
又回到了_dispatch_continuation_pop_inline
👇
此时肯定不会再次触发dx_invoke
,不然就递归了,所以会走到_dispatch_continuation_invoke_inline
,剩下的流程就是调用block()了。
至此,我们跟着底层源码弄清楚了block()的调用流程👇
- 通过在block任务中打断点,LLDB bt指令查看调用栈信息,找到
_dispatch_worker_thread2
;- 搜索调用
_dispatch_worker_thread2
的地方,找到_dispatch_root_queues_init
-->_dispatch_root_queues_init_once
;- 接着我们在
_dispatch_root_queues_init_once
中发现了子线程的创建,并绑定了block任务_dispatch_worker_thread2
;- 接着我们继续查看
_dispatch_worker_thread2
的底层源码,发现了调用block任务的时机点。
总结
我们通过异步并发
的案例,围绕两个点:子线程的创建时机
和任务Block的调用时机
,分析了dispatch_async
的底层大致流程,同时也关联到单例
的底层实现原理。
补充1:栅栏函数
dispatch_barrier_(a)sync
被称作栅栏函数
,不论是同步栅栏还是异步栅栏,都必须等上面的任务执行完,栅栏函数本身的block任务才会执行
,而同步与异步的差别在于👇
- 同步栅栏必须自己的block任务执行完成,下面的任务block才会执行,这就表示
同步阻塞的是当前的线程
。 - 异步却不需要等自己的任务block执行,下面的代码会接着执行,这就表示
异步阻塞的是队列(queue)
。
栅栏函数一个重要的点:必须是
并发队列
,并且是自定义的并发队列
。
不可用global_queue全局并发队列
示例👇
- (void)demo2{
// dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
// 这里是可以的额!
/* 1.异步函数 */
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"123");
});
/* 2. 栅栏函数 */ // - dispatch_barrier_sync
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
});
/* 3. 异步函数 */
dispatch_async(concurrentQueue, ^{
NSLog(@"加载那么多,喘口气!!!");
});
NSLog(@"**********起来干!!");
}
运行👇
栅栏根本没有作用,按道理123应该优先currentThead的。
作用可等同于锁
示例👇
- (void)demo3{
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10; i++) {
dispatch_async(concurrentQueue, ^{
NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
@synchronized (self) {
[self.mArray addObject:image];
NSLog(@"self.mArray添加image:%@", imageName);
}
});
}
}
运行👇
因为是异步并发队列,所以会有很多子线程处理图片的添加过程,但是日志表明,当前的图片是一张张的添加进数组的,这就是锁的作用。
我们再换成栅栏函数👇
dispatch_barrier_async(concurrentQueue , ^{
[self.mArray addObject:image];
NSLog(@"self.mArray添加image:%@", imageName);
});
运行👇
一样的效果。
补充2:同步函数
dispatch_sync 沿着调用链,发现,如果是串行队列
,那么就会走_dispatch_barrier_sync_f
。👇
接着看_dispatch_barrier_sync_f
的源码👇
再回头看看_dispatch_queue_try_acquire_barrier_sync
里的流程👇
补充3:死锁问题
在主线程中运行下面代码:
dispatch_sync(mainQueue, ^{
NSLog(@"123");
});
打断点,查看调用栈信息
会走到_dispatch_sync_f_slow
,再看源码👇
接着看_dispatch_lock_is_locked_by
至此,最终会执行DISPATCH_CLIENT_CRASH((uintptr_t)dq_state, "dispatch_sync called on queue " "already owned by current thread");
,直接crash报错。
这个错就是,主队列中添加同步执行的block任务
,这个任务会交给当前的主线程去处理执行
,但是因为是同步函数
,主线程
又要等待
block任务的执行完成
,才能接着往下走,说白了,就是block的执行等待自己block的完成
,矛盾了!这个矛盾的现象就被称作死锁
。
所以,这个死锁
具体跟哪个队列是没有关系
的,不管是主队列
,还是其它自定义的串行队列
,只要block任务自己等待自己完成
,就会crash死锁!
补充4:信号量
信号量dispatch_semaphore_t
实际运用的场景比较少,涉及的核心的函数有:
- dispatch_semaphore_create 创建信号量,指定最大并发数
- dispatch_semaphore_signal 发送信号
- dispatch_semaphore_wait 等待信号
示例👇
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务1");
sleep(1);
NSLog(@"任务1完成");
dispatch_semaphore_signal(sem);
});
//任务2
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务2");
sleep(1);
NSLog(@"任务2完成");
dispatch_semaphore_signal(sem);
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务3");
sleep(1);
NSLog(@"任务3完成");
dispatch_semaphore_signal(sem);
});
运行👇
信号量的发送与等待,配合使用,可以保证当前任务的按一定顺序去执行。
我们现在来看看,dispatch_semaphore_signal
和dispatch_semaphore_wait
底层源码是如何处理的?以dispatch_semaphore_signal
为例👇
dispatch_semaphore_signal
接着我们看看os_atomic_inc2o
👇
#define os_atomic_inc2o(p, f, m) \
os_atomic_add2o(p, f, 1, m)
os_atomic_add2o
👇
#define os_atomic_add2o(p, f, v, m) \
os_atomic_add(&(p)->f, (v), m)
带入值,可知👇
os_atomic_inc2o(dsema, dsema_value, release)
--> os_atomic_add2o(dsema, dsema_value, 1, release)
--> os_atomic_add(dsema_value, 1, release)
接着看os_atomic_add
#define os_atomic_add(p, v, m) \
_os_atomic_c11_op((p), (v), m, add, +)
#define _os_atomic_c11_op(p, v, m, o, op) \
({ _os_atomic_basetypeof(p) _v = (v), _r = \
atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
memory_order_##m); (__typeof__(_r))(_r op _v); })
接着:os_atomic_add(dsema_value, 1, release)
--> _os_atomic_c11_op(dsema_value, 1, release, add, +)
,然后_os_atomic_c11_op
是一个函数,重点看atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \ memory_order_##m)
这句,再带入入参值,就是:atomic_fetch_add_explicit(3个入参)
,atomic_fetch_add_explicit
是什么意思呢?👇详细请参考
综上,我们知道了
dispatch_semaphore_signal
就是++自加操作
。
dispatch_semaphore_wait
所以,dispatch_semaphore_signal表示信号量+1,dispatch_semaphore_wait表示信号量-1
。如果在+1之前,信号量是-1的话,结果是0,那么就会进入signal_slow信号量永久等待的过程,同理,在wait之前信号量是0的话,减1结果是负1,也会进入signal_slow信号量永久等待的过程。
补充5:调度组
现在有一个场景:我们先要做很多的异步操作,例如网络请求,需要等待所有的异步网络请求执行完成后,才能执行下一步的操作。我们都知道,网络请求的执行完成,依赖当前网络的状态
、服务器响应的速度
以及网络带宽的大小
等其它很多因素,每个网络请求完成的时间是无法把控的
,更何况要去找所有网络请求
执行完成
的这个时机点
呢?很难吧!如果此时采用栅栏函数
,你准备栏在哪里?此时,需要用到GDC另一个常用的API:dispatch_group_t调度组
。
我们先看看一个使用的示例:下载两张图片,其中一张是水印,两张图片下载完成后,再将两张图片整合生成一张带水印的图片展示👇
- (void)groupDemo {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
//创建调度组
NSString *logoStr1 = @"https://f12.baidu.com/it/u=711217113,818398466&fm=72";
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
UIImage *image1 = [UIImage imageWithData:data1];
[self.mArray addObject:image1];
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
//创建调度组
NSString *logoStr2 = @"https://f12.baidu.com/it/u=711217113,818398466&fm=72";
NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
UIImage *image2 = [UIImage imageWithData:data2];
[self.mArray addObject:image2];
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
UIImage *newImage = nil;
NSLog(@"数组个数:%ld",self.mArray.count);
for (int i = 0; i<self.mArray.count; i++) {
UIImage *waterImage = self.mArray[i];
newImage =[KC_ImageTool kc_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
}
self.imageView.image = newImage;
});
}
添加水印的代码👇
/**
给指定图片加图片水印
@param waterImage 水印图片
@param rect 位置
@return 返回图片水印照片
*/
+ (UIImage *)kc_WaterImageWithWaterImage:(UIImage *)waterImage backImage:(UIImage *)backImage waterImageRect:(CGRect)rect{
UIImage *newBackImage = backImage;
if (!newBackImage) {
newBackImage = [UIImage imageNamed:@"backImage"];
}
return [self kc_WaterImageWithImage:newBackImage waterImage:waterImage waterImageRect:rect];
}
// 给图片添加图片水印
+ (UIImage *)kc_WaterImageWithImage:(UIImage *)image waterImage:(UIImage *)waterImage waterImageRect:(CGRect)rect{
//1.获取图片
//2.开启上下文
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
//3.绘制背景图片
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
//绘制水印图片到当前上下文
[waterImage drawInRect:rect];
//4.从上下文中获取新图片
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
//5.关闭图形上下文
UIGraphicsEndImageContext();
//返回图片
return newImage;
}
特殊1
再看一个相对简单的案例,但是我们不按常理出牌,将dispatch_group_notify
写到最前面👇
- (void)groupDemo2{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"他们回来了,我准备在主线程更新UI");
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
sleep(1); // 模拟耗时
NSLog(@"123");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"456");
dispatch_group_leave(group);
});
NSLog(@"主线程任务正常运行");
}
run👇
事实证明-->把notify
写在前面,发现会提前调用notify
。why?
特殊2
接着我们把enter多写一个
👇
- (void)groupDemo2{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
sleep(1); // 模拟耗时
NSLog(@"123");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"456");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"他们回来了,我准备在主线程更新UI");
});
NSLog(@"主线程任务正常运行");
}
run👇
可见,enter
多了时,主线程被卡死了,但是没有crash。
特殊3
我们再多写一个leave
,👇
- (void)groupDemo2{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
sleep(1); // 模拟耗时
NSLog(@"123");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"456");
dispatch_group_leave(group);
});
dispatch_group_leave(group);
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"他们回来了,我准备在主线程更新UI");
});
NSLog(@"主线程任务正常运行");
}
run👇
leave
多了时,直接crash!
特殊4
我们使用dispatch_group_async
,看看是打印什么?
- (void)groupDemo2{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
sleep(1); // 模拟耗时
NSLog(@"123");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"456");
dispatch_group_leave(group);
});
dispatch_group_async(group, queue, ^{
NSLog(@"7890");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"他们回来了,我准备在主线程更新UI");
});
NSLog(@"主线程任务正常运行");
}
run👇
根据打印的日志,因为第一个group_async
里有做一个sleep(1)
,所以456先打印,紧接着主线程的日志打印,再是dispatch_group_async
的7890打印,然后123打印,最后notify
的日志打印,那么就说明,在有dispatch_group_async
的情况下,notify
依旧最后执行,与有enter
和leave
的情况是一样的。
带着上面几种现象,我们总结了以下3个问题👇
dispatch_group_enter
dispatch_group_leave
还有group_notify
底层源码处理了哪些流程?dispatch_group_notify
是如何接收dispatch_group_enter
和dispatch_group_leave
传递的信息,然后执行block任务?- 还有
dispatch_group_async
的底层源码做了什么?
dispatch_group_create底层
我们先看看dispatch_group_create
dispatch_group_t
dispatch_group_create(void)
{
return _dispatch_group_create_with_count(0);
}
dispatch_group_enter底层
看看
os_atomic_sub_orig2o
👇
#define os_atomic_sub_orig2o(p, f, v, m) \
os_atomic_sub_orig(&(p)->f, (v), m)
#define os_atomic_sub_orig(p, v, m) \
_os_atomic_c11_op_orig((p), (v), m, sub, -)
#define _os_atomic_c11_op_orig(p, v, m, o, op) \
atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
memory_order_##m)
注意第4个入参,和之前发送信号量dispatch_semaphore_signal
的宏定义处理基本一模一样,那么得到的拼接函数名是atomic_fetch_sub_explicit
👇
如果得到的old_value是0
,那么接着进入_dispatch_retain
👇
DISPATCH_ALWAYS_INLINE_NDEBUG
static inline void
_dispatch_retain(dispatch_object_t dou)
{
(void)_os_object_retain_internal_n_inline(dou._os_obj, 1);
}
#define _os_object_refcnt_add_orig(o, n) \
_os_atomic_refcnt_add_orig2o(o, os_obj_ref_cnt, n)
#define _os_atomic_refcnt_add_orig2o(o, m, n) \
_os_atomic_refcnt_perform2o(o, m, add_orig, n, relaxed)
#define _os_atomic_refcnt_perform2o(o, f, op, n, m) ({ \
__typeof__(o) _o = (o); \
int _ref_cnt = _o->f; \
if (likely(_ref_cnt != _OS_OBJECT_GLOBAL_REFCNT)) { \
_ref_cnt = os_atomic_##op##2o(_o, f, n, m); \
} \
_ref_cnt; \
})
同样的替换,得到os_atomic_add_orig2o
#define os_atomic_add_orig2o(p, f, v, m) \
os_atomic_add_orig(&(p)->f, (v), m)
#define os_atomic_add_orig(p, v, m) \
_os_atomic_c11_op_orig((p), (v), m, add, +)
#define _os_atomic_c11_op_orig(p, v, m, o, op) \
atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
memory_order_##m)
再次替换,得到atomic_fetch_add_explicit
,又是这个加1操作。
至此验证了,当从os底层哈希表中获取的当前group的INTERVAL值为0时
,走_dispatch_retain
,会+1,类似于一个加锁的操作,一直在此wait。
dispatch_group_leave底层
接着看看
os_atomic_add_orig2o
#define os_atomic_add_orig2o(p, f, v, m) \
os_atomic_add_orig(&(p)->f, (v), m)
#define os_atomic_add_orig(p, v, m) \
_os_atomic_c11_op_orig((p), (v), m, add, +)
#define _os_atomic_c11_op_orig(p, v, m, o, op) \
atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
memory_order_##m)
拼接函数名,得到atomic_fetch_add_explicit
--> +1操作,和注释描述的一样incremented -1 -> 0 transition
。
下图是一个特殊情况,当enter多次时的处理👇
再接着看看_dispatch_group_wake
至此,我们知道了dispatch_group_enter
减一 dispatch_group_leave
加一,同时,当从os底层获取group的INTERVAL的值为DISPATCH_GROUP_VALUE_1
时,会执行一个do-while循环处理新旧状态值,最终dispatch_group_leave
会调用_dispatch_group_wake
通知group
的任务block
继续往下执行。
dispatch_group_notify底层
通过对dispatch_group_enter
和dispatch_group_leave
的底层源码分析得知,只有leave
时,old_value
才会从-1 --> 0,所以dispatch_group_leave
后,dispatch_group_notify
才会调用_dispatch_group_wake
通知group
的任务block
继续往下执行。这里也就回答了问题1
和问题2
的原因所在。
dispatch_group_async的底层
最后看看dispatch_group_async
的源码👇
_dispatch_continuation_async
我们再熟悉不过,之前在分析dispatch_async
时,调用栈里也有它👇
接着dx_push
,它会创建子线程,绑定任务_dispatch_worker_thread2
,而_dispatch_worker_thread2
就是负责调用group任务block的所在。
那么问题来了,_dispatch_continuation_async
里有了dispatch_group_enter
,却没有dispatch_group_leave
,leave在哪里被调用呢?猜想,是否在任务block执行完成后?我们不妨验证一下,在block里打断点,bt查看其调用栈信息👇
_dispatch_call_block_and_release
这个明显是block执行完成,release释放了,所以往下看_dispatch_client_callout
,这个我们也知道,是触发任务block,再往下,看看_dispatch_queue_override_invoke
👇
通过bt查看调用栈信息,查找到_dispatch_queue_override_invoke
里,然后继续往下查找,最终找到了dispatch_group_leave
。
综上我们可以得出结论👇
dispatch_group_async
=dispatch_group_enter
+子线程绑定,block任务调用
+dispatch_group_leave
以上就是对调度组dispatch_group_t
几个常用api的底层源码的分析,我们现在已经清楚的知道了dispatch_group_enter
和 dispatch_group_leave
必须配对使用,而dispatch_group_notify
的执行,必须依赖dispatch_group_leave
的唤醒。同时,dispatch_group_async
就是简化版的dispatch_group_enter
和 dispatch_group_leave
。