回顧
在上篇博客對GCD的不同的隊列繼續(xù)了底層的源碼探索分析, 那么本篇博客將繼續(xù)對GCD的函數(shù)繼續(xù)源碼分析。

1. sync 同步函數(shù)
我們都知道
GCD底層是用C寫的,封裝了block函數(shù)來執(zhí)行添加的任務(wù),那么這個block底層是如何封裝的呢?
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"GCD函數(shù)分析");
});
在源碼里面搜索
dispatch_sync
我們看的是
block也就是第二個參數(shù)work,直接看work去哪里了就行,直接定位在最后一行代碼
_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
搜索
_dispatch_Block_invoke
- 來自一個
dispatch_function_t類型的,結(jié)構(gòu)體Block_layout *的invoke
那么現(xiàn)在去看看這 block的包裝函數(shù)_dispatch_sync_f在哪里調(diào)用了,通過搜索找到了一個中間層包裝,如下代碼:
static void
_dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func,
uintptr_t dc_flags)
{
_dispatch_sync_f_inline(dq, ctxt, func, dc_flags);
}
再去搜索_dispatch_sync_f_inline
_dispatch_sync_f_inline里面if判斷太多了,不知道該看哪個了,改怎么辦呢?這個源碼是不能編譯運(yùn)行的!靚仔,不要慌!我們可以在調(diào)用函數(shù)的地方,下不同的符號斷點(diǎn),看看走哪個??
-
下符斷點(diǎn)
下符合斷點(diǎn) - 運(yùn)行看看走哪個
通過符合斷點(diǎn)定位
通過下符合斷點(diǎn),很清晰的可以看到,走了_dispatch_sync_f_slow,也就是下面圖中代碼處:
通過符合斷點(diǎn)定位到此處
然后繼續(xù)在源碼里面搜索_dispatch_sync_f_slow
-
_dispatch_sync_f_slow
_dispatch_sync_f_slow
這又不知道該走哪里,再次下符合斷點(diǎn),發(fā)現(xiàn)走到了_dispatch_sync_function_invoke執(zhí)行,那么再繼續(xù)搜索 -
_dispatch_sync_function_invoke
_dispatch_sync_function_invoke
我們找誰使用了這ctxt、func兩個參數(shù),發(fā)現(xiàn)是這句代碼
_dispatch_client_callout(ctxt, func)那么現(xiàn)在去搜索一下 -
_dispatch_client_callout
_dispatch_client_callout
把自己傳入返回,和block的底層是一樣的調(diào)用方式,這也就說明了在調(diào)用_dispatch_client_callout的時候,異步函數(shù)會執(zhí)行block,驗證如下:
打印調(diào)用堆棧
通過斷點(diǎn)在block函數(shù)執(zhí)行體處,再通過bt打印調(diào)用堆棧,可以很明顯的看到,在_dispatch_client_callout函數(shù)執(zhí)行后,才會執(zhí)行block函數(shù)體內(nèi)。
這一波操作,就很細(xì)節(jié),666,還有誰能把GCD源碼探索的這么清新脫俗,45 度仰望天空,我這該死的無處安放的魅力!
2. asycn 異步函數(shù)
異步也是一樣, 那么我們來搜索一波
-
dispatch_async
dispatch_async
還是一樣找到work在哪里使用了,由此定位到qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags)這句代碼,再搜索_dispatch_continuation_init -
_dispatch_continuation_init
_dispatch_continuation_init - 可以發(fā)現(xiàn)如下代碼,對
work處理,返回了func,再傳入_dispatch_continuation_init_f中,繼續(xù)搜索_dispatch_continuation_init_f
_dispatch_continuation_init_f
在前面的那個,是直接返回了,但是這里做了一層包裝
dc->dc_func = f;
dc->dc_ctxt = ctxt;
包裝完了,還有一個對優(yōu)先級的處理
return _dispatch_continuation_priority_set(dc, dqu, pp, flags);
_dispatch_continuation_priority_set
我們再回到
dispatch_async函數(shù)里面
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);
}
這里面就是對執(zhí)行的任務(wù)設(shè)置了優(yōu)先級的封裝,那么蘋果為什么要在異步的里面做這么個封裝呢?
- 異步函數(shù)代表異步調(diào)用
- 異步是無序的調(diào)用
- 異步的回調(diào)也是異步的,會根據(jù)
CPU的調(diào)度在適當(dāng)?shù)臅r候異步回調(diào) - 也是函數(shù)式編程的一中體現(xiàn),就是在需要的時候進(jìn)行回調(diào)
但是這里最重要的還是最后一行代碼
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
_dispatch_continuation_async
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION
if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
_dispatch_trace_item_push(dqu, dc);
}
#else
(void)dc_flags;
#endif
return dx_push(dqu._dq, dc, qos);
}
這里就會迷失方向了,這個dx_push是個什么東西呢?搜索了下,找到了下面這個宏定義
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
在宏定義里面dx_vtable不是我們需要看的,我們?nèi)フ?code>dq_push,這個第三個參數(shù) z 就是 qos,看看dq_push在哪里使用了
底層為不同類型的隊列提供不同的調(diào)用入口,比如全局并發(fā)隊列會調(diào)用
_dispatch_root_queue_push方法。依此作為入口,全局搜索_dispatch_root_queue_push方法的實現(xiàn):
前面的代碼只是做一些判斷封裝處理,最終會走到最后一行代碼
_dispatch_root_queue_push_inline中,繼續(xù)跟蹤器源碼流程:
-
_dispatch_root_queue_push_inline
_dispatch_root_queue_push_inline
繼續(xù)跟蹤_dispatch_root_queue_poke -
_dispatch_root_queue_poke
_dispatch_root_queue_poke -
_dispatch_root_queue_poke_slow
_dispatch_root_queue_poke_slow
這么多代碼,看了一遍沒有找到什么關(guān)鍵信息,在6295行代碼,_dispatch_root_queues_init方法里面有關(guān)鍵信息
_dispatch_root_queues_init(void)
{
dispatch_once_f(&_dispatch_root_queues_pred, NULL,
_dispatch_root_queues_init_once);
}
這里是一個dispatch_once_f單例,有個參數(shù)_dispatch_root_queues_init_once,繼續(xù)搜索
-
_dispatch_root_queues_init_once
_dispatch_root_queues_init_once
在該方法中進(jìn)行了線程池的初始化處理、工作隊列的配置、初始化等工作,這也就是解釋了為什么_dispatch_root_queues_init_once是單例的。單例可以避免重復(fù)的初始化。
同時這里有一個關(guān)鍵的設(shè)置,執(zhí)行函數(shù)的設(shè)置,也就是將任務(wù)執(zhí)行的函數(shù)被統(tǒng)一設(shè)置成了_dispatch_worker_thread2 ,如下代碼:
cfg.workq_cb = _dispatch_worker_thread2;
r = pthread_workqueue_setup(&cfg, sizeof(cfg));
通過bt打印程序的運(yùn)行堆棧信息,來驗證異步函數(shù)最終任務(wù)是通過_dispatch_worker_thread2調(diào)用的
控制臺打印的堆棧信息,和我們探索推理的結(jié)果是,一模模一樣樣??,就問靚仔你服不服!哈哈??!
3. 總結(jié)
-
GCD源碼難懂,可以通過一些返回值和關(guān)鍵信息推理 - 通過 下
符號斷點(diǎn),追蹤代碼調(diào)用邏輯 - 通過
bt打印堆棧驗證探索結(jié)果
更多內(nèi)容持續(xù)更新
?? 喜歡就點(diǎn)個贊吧????
?? 覺得有收獲的,可以來一波,收藏+關(guān)注,評論 + 轉(zhuǎn)發(fā),以免你下次找不到我????
??歡迎大家留言交流,批評指正,互相學(xué)習(xí)??,提升自我??