iOS底層探索之多線程(六)—GCD源碼分析(sync 同步函數(shù)、async 異步函數(shù))

回顧

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

多線程.png

1. sync 同步函數(shù)

我們都知道 GCD底層是用C寫的,封裝了 block函數(shù)來執(zhí)行添加的任務(wù),那么這個 block底層是如何封裝的呢?

dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"GCD函數(shù)分析");
   });

在源碼里面搜索dispatch_sync

dispatch_sync

我們看的是block也就是第二個參數(shù)work,直接看 work去哪里了就行,直接定位在最后一行代碼

_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);

搜索_dispatch_Block_invoke

_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

_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 度仰望天空,我這該死的無處安放的魅力!

666

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_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是個什么東西呢?搜索了下,找到了下面這個宏定義

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在哪里使用了

dq_push調(diào)用

底層為不同類型的隊列提供不同的調(diào)用入口,比如全局并發(fā)隊列會調(diào)用_dispatch_root_queue_push方法。依此作為入口,全局搜索_dispatch_root_queue_push方法的實現(xiàn):

_dispatch_root_queue_push

前面的代碼只是做一些判斷封裝處理,最終會走到最后一行代碼_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)用的

_dispatch_worker_thread2

控制臺打印的堆棧信息,和我們探索推理的結(jié)果是,一模模一樣樣??,就問靚仔你服不服!哈哈??!
還有誰

3. 總結(jié)

  • GCD源碼難懂,可以通過一些返回值和關(guān)鍵信息推理
  • 通過 下符號斷點(diǎn),追蹤代碼調(diào)用邏輯
  • 通過 bt 打印堆棧驗證探索結(jié)果

更多內(nèi)容持續(xù)更新

?? 喜歡就點(diǎn)個贊吧????

?? 覺得有收獲的,可以來一波,收藏+關(guān)注,評論 + 轉(zhuǎn)發(fā),以免你下次找不到我????

??歡迎大家留言交流,批評指正,互相學(xué)習(xí)??,提升自我??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容