前言
書接上回函數(shù)與隊列酝惧,我們根據(jù)GCD
底層源碼分析芭碍,知道了隊列分為串行
和并發(fā)
兩種類型颓屑,有兩種常用的隊列:主隊列
和全局隊列
官脓,其中主隊列類型是串行
仿贬,而全局隊列類型是并發(fā)
茁瘦。
隊列的底層創(chuàng)建流程大致分析完了品抽,今天我們再重點看看函數(shù)的底層執(zhí)行流程
,就是GCD的Block的初始化甜熔,以及被調(diào)用的過程圆恤。我們以異步并發(fā)
為例看看??
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
- 入?yún)ork就是block,需要執(zhí)行的任務(wù);
- 局部變量
dc
dc_flag
都是為了初始化qos
-->_dispatch_continuation_init(dc, dq, work, 0, dc_flags);
- 初始化完成后盆昙,接著
_dispatch_continuation_async
dispatch_async的源碼不多羽历,就這么幾行,但是我們知道淡喜,異步并發(fā)任務(wù)是會開啟子線程的秕磷,在子線程去執(zhí)行block任務(wù),所以炼团,我們需要關(guān)注2個點:
- 子線程創(chuàng)建的時機點
- 任務(wù)block執(zhí)行的時機點
1 子線程的創(chuàng)建流程
我們先進(jìn)入_dispatch_continuation_init
的流程澎嚣,看看是否有子線程創(chuàng)建?
1.1 _dispatch_continuation_init 底層流程
接著來到_dispatch_continuation_init_f
瘟芝,其中release的func是第4個入?yún)⒁滋遥鴚ork就是需要執(zhí)行的任務(wù)是ctxt第3個入?yún)ⅲ^續(xù)看看_dispatch_continuation_init_f
- _dispatch_continuation_init_f
- _dispatch_continuation_voucher_set
-
_dispatch_continuation_priority_set
--> dc的dc_priority的設(shè)置 ??
以上并沒有發(fā)現(xiàn)關(guān)于子線程創(chuàng)建的代碼锌俱,接下來我們?nèi)?code>_dispatch_continuation_async找找晤郑。
1.2 _dispatch_continuation_async底層流程
根據(jù)返回值,鎖定到了dx_push
贸宏。全局搜索dx_push
??
繼續(xù)看dq_push
??
如果是
并發(fā)隊列
造寝,那么.dq_push = _dispatch_lane_concurrent_push
,接著來到_dispatch_lane_concurrent_push
??
顯然是非柵欄函數(shù),那么進(jìn)入_dispatch_continuation_redirect_push
??
那do_targetq
是什么呢吭练?得回到隊列的創(chuàng)建dispatch_queue_create
去查看??
那么匹舞,_dispatch_continuation_redirect_push
里的dx_push
時的隊列是_dispatch_get_root_queue()
??
同理,找dispatch_queue_global_t
對應(yīng)的 dq_push
的方法
至此线脚,我們知道了
dispatch_async
中子線程創(chuàng)建
的調(diào)用鏈??(層級比較深)
dispatch_async
-->_dispatch_continuation_async
-->dx_push
-->dq_push
--> 并發(fā)隊列:_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
-->線程池調(diào)度,創(chuàng)建線程pthread_create
2 任務(wù)Block的調(diào)用流程
那么接下來的問題就是 block何時調(diào)用?
再看哪里調(diào)用的block --> 類似這樣的代碼block(xxx)
? 我們可以在dispatch_async的任務(wù)block中打斷點叫榕,然后bt查看調(diào)用棧??
注意到在調(diào)用棧中有一個_dispatch_worker_thread2
浑侥,現(xiàn)在重點就來到 --> 什么時候調(diào)起的_dispatch_worker_thread2
?
我們先全局搜索一下_dispatch_worker_thread2
??
發(fā)現(xiàn)全在一個方法里面 --> _dispatch_root_queues_init_once
中晰绎,同理寓落,全局搜索??
再全局搜索_dispatch_root_queues_init
??
_dispatch_root_queue_poke_slow
是否很熟悉? 就是我們上面在查找創(chuàng)建子線程
時調(diào)用棧走過的方法荞下,那么此時任務(wù)block的調(diào)用和子線程的創(chuàng)建產(chǎn)生了聯(lián)系
伶选,這個聯(lián)系
就是_dispatch_root_queue_poke_slow
。
2.1 _dispatch_root_queues_init
現(xiàn)在我們重點來看看_dispatch_root_queues_init
尖昏,是否真的調(diào)用任務(wù)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
判斷條件迹淌,就能保證當(dāng)前只有一個線程進(jìn)去執(zhí)行代碼河绽,那為什么只能執(zhí)行一次呢己单?還是看_dispatch_once_gate_broadcast
里的 _dispatch_once_mark_done
2.3 回到Block的調(diào)用時機
即什么時候調(diào)起的_dispatch_worker_thread2
?
在_dispatch_root_queues_init
時耙饰,單例執(zhí)行的任務(wù)block是_dispatch_root_queues_init_once
??
再來看看
_dispatch_root_queues_init_once
??
上圖可知纹笼,在_dispatch_root_queues_init_once
中完成了線程與任務(wù)_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
??
因為是并發(fā)隊列queue_concurrent,對應(yīng)的invoke方法名是_dispatch_lane_invoke
??
invoke是入?yún)?code>_dispatch_queue_class_invoke_handler_t類型拨齐,也就是方法_dispatch_lane_invoke2
??
又回到了_dispatch_continuation_pop_inline
??
此時肯定不會再次觸發(fā)dx_invoke
鳞陨,不然就遞歸了,所以會走到_dispatch_continuation_invoke_inline
瞻惋,剩下的流程就是調(diào)用block()了厦滤。
至此,我們跟著底層源碼弄清楚了block()的調(diào)用流程??
- 通過在block任務(wù)中打斷點歼狼,LLDB bt指令查看調(diào)用棧信息掏导,找到
_dispatch_worker_thread2
;- 搜索調(diào)用
_dispatch_worker_thread2
的地方羽峰,找到_dispatch_root_queues_init
-->_dispatch_root_queues_init_once
趟咆;- 接著我們在
_dispatch_root_queues_init_once
中發(fā)現(xiàn)了子線程的創(chuàng)建,并綁定了block任務(wù)_dispatch_worker_thread2
梅屉;- 接著我們繼續(xù)查看
_dispatch_worker_thread2
的底層源碼值纱,發(fā)現(xiàn)了調(diào)用block任務(wù)的時機點。
總結(jié)
我們通過異步并發(fā)
的案例坯汤,圍繞兩個點:子線程的創(chuàng)建時機
和任務(wù)Block的調(diào)用時機
虐唠,分析了dispatch_async
的底層大致流程,同時也關(guān)聯(lián)到單例
的底層實現(xiàn)原理惰聂。
補充1:柵欄函數(shù)
dispatch_barrier_(a)sync
被稱作柵欄函數(shù)
疆偿,不論是同步柵欄還是異步柵欄,都必須等上面的任務(wù)執(zhí)行完搓幌,柵欄函數(shù)本身的block任務(wù)才會執(zhí)行
杆故,而同步與異步的差別在于??
- 同步柵欄必須自己的block任務(wù)執(zhí)行完成,下面的任務(wù)block才會執(zhí)行鼻种,這就表示
同步阻塞的是當(dāng)前的線程
反番。 - 異步卻不需要等自己的任務(wù)block執(zhí)行,下面的代碼會接著執(zhí)行,這就表示
異步阻塞的是隊列(queue)
罢缸。
柵欄函數(shù)一個重要的點:必須是
并發(fā)隊列
篙贸,并且是自定義的并發(fā)隊列
。
不可用global_queue全局并發(fā)隊列
示例??
- (void)demo2{
// dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
// 這里是可以的額!
/* 1.異步函數(shù) */
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"123");
});
/* 2. 柵欄函數(shù) */ // - dispatch_barrier_sync
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
});
/* 3. 異步函數(shù) */
dispatch_async(concurrentQueue, ^{
NSLog(@"加載那么多,喘口氣!!!");
});
NSLog(@"**********起來干!!");
}
運行??
柵欄根本沒有作用枫疆,按道理123應(yīng)該優(yōu)先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);
}
});
}
}
運行??
因為是異步并發(fā)隊列,所以會有很多子線程處理圖片的添加過程息楔,但是日志表明寝贡,當(dāng)前的圖片是一張張的添加進(jìn)數(shù)組的,這就是鎖的作用值依。
我們再換成柵欄函數(shù)??
dispatch_barrier_async(concurrentQueue , ^{
[self.mArray addObject:image];
NSLog(@"self.mArray添加image:%@", imageName);
});
運行??
一樣的效果圃泡。
補充2:同步函數(shù)
dispatch_sync 沿著調(diào)用鏈,發(fā)現(xiàn)愿险,如果是串行隊列
颇蜡,那么就會走_dispatch_barrier_sync_f
。??
接著看_dispatch_barrier_sync_f
的源碼??
再回頭看看_dispatch_queue_try_acquire_barrier_sync
里的流程??
補充3:死鎖問題
在主線程中運行下面代碼:
dispatch_sync(mainQueue, ^{
NSLog(@"123");
});
打斷點辆亏,查看調(diào)用棧信息
會走到_dispatch_sync_f_slow
风秤,再看源碼??
接著看_dispatch_lock_is_locked_by
至此,最終會執(zhí)行DISPATCH_CLIENT_CRASH((uintptr_t)dq_state, "dispatch_sync called on queue " "already owned by current thread");
扮叨,直接crash報錯缤弦。
這個錯就是,主隊列中添加同步執(zhí)行的block任務(wù)
彻磁,這個任務(wù)會交給當(dāng)前的主線程去處理執(zhí)行
碍沐,但是因為是同步函數(shù)
,主線程
又要等待
block任務(wù)的執(zhí)行完成
衷蜓,才能接著往下走抢韭,說白了,就是block的執(zhí)行等待自己block的完成
恍箭,矛盾了!這個矛盾的現(xiàn)象就被稱作死鎖
瞧省。
所以扯夭,這個死鎖
具體跟哪個隊列是沒有關(guān)系
的,不管是主隊列
鞍匾,還是其它自定義的串行隊列
交洗,只要block任務(wù)自己等待自己完成
,就會crash死鎖橡淑!
補充4:信號量
信號量dispatch_semaphore_t
實際運用的場景比較少构拳,涉及的核心的函數(shù)有:
- dispatch_semaphore_create 創(chuàng)建信號量,指定最大并發(fā)數(shù)
- dispatch_semaphore_signal 發(fā)送信號
- dispatch_semaphore_wait 等待信號
示例??
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
//任務(wù)1
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"執(zhí)行任務(wù)1");
sleep(1);
NSLog(@"任務(wù)1完成");
dispatch_semaphore_signal(sem);
});
//任務(wù)2
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"執(zhí)行任務(wù)2");
sleep(1);
NSLog(@"任務(wù)2完成");
dispatch_semaphore_signal(sem);
});
//任務(wù)3
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"執(zhí)行任務(wù)3");
sleep(1);
NSLog(@"任務(wù)3完成");
dispatch_semaphore_signal(sem);
});
運行??
信號量的發(fā)送與等待,配合使用置森,可以保證當(dāng)前任務(wù)的按一定順序去執(zhí)行斗埂。
我們現(xiàn)在來看看,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
是一個函數(shù)行贪,重點看atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \ memory_order_##m)
這句漾稀,再帶入入?yún)⒅担褪牵?code>atomic_fetch_add_explicit(3個入?yún)?建瘫,atomic_fetch_add_explicit
是什么意思呢崭捍???詳細(xì)請參考
綜上,我們知道了
dispatch_semaphore_signal
就是++自加操作
啰脚。
dispatch_semaphore_wait
所以殷蛇,dispatch_semaphore_signal表示信號量+1,dispatch_semaphore_wait表示信號量-1
拣播。如果在+1之前晾咪,信號量是-1的話,結(jié)果是0贮配,那么就會進(jìn)入signal_slow信號量永久等待的過程谍倦,同理,在wait之前信號量是0的話泪勒,減1結(jié)果是負(fù)1昼蛀,也會進(jìn)入signal_slow信號量永久等待的過程。
補充5:調(diào)度組
現(xiàn)在有一個場景:我們先要做很多的異步操作圆存,例如網(wǎng)絡(luò)請求叼旋,需要等待所有的異步網(wǎng)絡(luò)請求執(zhí)行完成后,才能執(zhí)行下一步的操作沦辙。我們都知道夫植,網(wǎng)絡(luò)請求的執(zhí)行完成,依賴當(dāng)前網(wǎng)絡(luò)的狀態(tài)
油讯、服務(wù)器響應(yīng)的速度
以及網(wǎng)絡(luò)帶寬的大小
等其它很多因素详民,每個網(wǎng)絡(luò)請求完成的時間是無法把控的
,更何況要去找所有網(wǎng)絡(luò)請求
執(zhí)行完成
的這個時機點
呢陌兑?很難吧沈跨!如果此時采用柵欄函數(shù)
,你準(zhǔn)備欄在哪里兔综?此時饿凛,需要用到GDC另一個常用的API:dispatch_group_t調(diào)度組
狞玛。
我們先看看一個使用的示例:下載兩張圖片,其中一張是水印涧窒,兩張圖片下載完成后心肪,再將兩張圖片整合生成一張帶水印的圖片展示??
- (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, ^{
//創(chuàng)建調(diào)度組
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, ^{
//創(chuàng)建調(diào)度組
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(@"數(shù)組個數(shù):%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)];
//繪制水印圖片到當(dāng)前上下文
[waterImage drawInRect:rect];
//4.從上下文中獲取新圖片
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
//5.關(guān)閉圖形上下文
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(@"他們回來了蒙畴,我準(zhǔn)備在主線程更新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(@"主線程任務(wù)正常運行");
}
run??
事實證明-->把notify
寫在前面,發(fā)現(xiàn)會提前調(diào)用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(@"他們回來了,我準(zhǔn)備在主線程更新UI");
});
NSLog(@"主線程任務(wù)正常運行");
}
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(@"他們回來了,我準(zhǔn)備在主線程更新UI");
});
NSLog(@"主線程任務(wù)正常運行");
}
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(@"他們回來了苔埋,我準(zhǔn)備在主線程更新UI");
});
NSLog(@"主線程任務(wù)正常運行");
}
run??
根據(jù)打印的日志,因為第一個group_async
里有做一個sleep(1)
蜒犯,所以456先打印逻淌,緊接著主線程的日志打印马澈,再是dispatch_group_async
的7890打印小压,然后123打印丁逝,最后notify
的日志打印,那么就說明淘菩,在有dispatch_group_async
的情況下遵班,notify
依舊最后執(zhí)行,與有enter
和leave
的情況是一樣的潮改。
帶著上面幾種現(xiàn)象费奸,我們總結(jié)了以下3個問題??
dispatch_group_enter
dispatch_group_leave
還有group_notify
底層源碼處理了哪些流程?dispatch_group_notify
是如何接收dispatch_group_enter
和dispatch_group_leave
傳遞的信息进陡,然后執(zhí)行block任務(wù)?- 還有
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個入?yún)⒅壕危椭鞍l(fā)送信號量dispatch_semaphore_signal
的宏定義處理基本一模一樣缨历,那么得到的拼接函數(shù)名是atomic_fetch_sub_explicit
??
如果得到的old_value是0
,那么接著進(jìn)入_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操作赡磅。
至此驗證了魄缚,當(dāng)從os底層哈希表中獲取的當(dāng)前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)
拼接函數(shù)名嚼隘,得到atomic_fetch_add_explicit
--> +1操作,和注釋描述的一樣incremented -1 -> 0 transition
袒餐。
下圖是一個特殊情況飞蛹,當(dāng)enter多次時的處理??
再接著看看_dispatch_group_wake
至此,我們知道了dispatch_group_enter
減一 dispatch_group_leave
加一灸眼,同時卧檐,當(dāng)從os底層獲取group的INTERVAL的值為DISPATCH_GROUP_VALUE_1
時,會執(zhí)行一個do-while循環(huán)處理新舊狀態(tài)值焰宣,最終dispatch_group_leave
會調(diào)用_dispatch_group_wake
通知group
的任務(wù)block
繼續(xù)往下執(zhí)行霉囚。
dispatch_group_notify底層
通過對dispatch_group_enter
和dispatch_group_leave
的底層源碼分析得知,只有leave
時宛徊,old_value
才會從-1 --> 0佛嬉,所以dispatch_group_leave
后,dispatch_group_notify
才會調(diào)用_dispatch_group_wake
通知group
的任務(wù)block
繼續(xù)往下執(zhí)行闸天。這里也就回答了問題1
和問題2
的原因所在暖呕。
dispatch_group_async的底層
最后看看dispatch_group_async
的源碼??
_dispatch_continuation_async
我們再熟悉不過,之前在分析dispatch_async
時苞氮,調(diào)用棧里也有它??
接著dx_push
湾揽,它會創(chuàng)建子線程,綁定任務(wù)_dispatch_worker_thread2
笼吟,而_dispatch_worker_thread2
就是負(fù)責(zé)調(diào)用group任務(wù)block的所在库物。
那么問題來了,_dispatch_continuation_async
里有了dispatch_group_enter
贷帮,卻沒有dispatch_group_leave
戚揭,leave在哪里被調(diào)用呢?猜想撵枢,是否在任務(wù)block執(zhí)行完成后民晒?我們不妨驗證一下精居,在block里打斷點,bt查看其調(diào)用棧信息??
_dispatch_call_block_and_release
這個明顯是block執(zhí)行完成潜必,release釋放了靴姿,所以往下看_dispatch_client_callout
,這個我們也知道磁滚,是觸發(fā)任務(wù)block佛吓,再往下,看看_dispatch_queue_override_invoke
??
通過bt查看調(diào)用棧信息垂攘,查找到_dispatch_queue_override_invoke
里维雇,然后繼續(xù)往下查找,最終找到了dispatch_group_leave
搜贤。
綜上我們可以得出結(jié)論??
dispatch_group_async
=dispatch_group_enter
+子線程綁定谆沃,block任務(wù)調(diào)用
+dispatch_group_leave
以上就是對調(diào)度組dispatch_group_t
幾個常用api的底層源碼的分析,我們現(xiàn)在已經(jīng)清楚的知道了dispatch_group_enter
和 dispatch_group_leave
必須配對使用仪芒,而dispatch_group_notify
的執(zhí)行唁影,必須依賴dispatch_group_leave
的喚醒。同時掂名,dispatch_group_async
就是簡化版的dispatch_group_enter
和 dispatch_group_leave
据沈。