GCD之函數(shù)與隊(duì)列初探

一、前言

在iOS開發(fā)過程中,我們知道多線程技術(shù)是使用最多的情況,能快速的執(zhí)行多個(gè)調(diào)度任務(wù)的執(zhí)行。而在多線程開發(fā)過程當(dāng)中,多線程技術(shù)有好幾種,其中包括pthreadNSThread,NSOperationGCD,而GCD是整個(gè)iOS開發(fā)過程中使用最多的也是最安全的一種技術(shù),因?yàn)镚CD是基于C/C++函數(shù)的封裝實(shí)現(xiàn),因此線程比較安全,在多線程開發(fā)過程中為我們開發(fā)者省去了基于考慮線程安全的事情,專注開發(fā)。是多線程開發(fā)過程中的首選。

然而GCD

*1 是如何來分配多線程的調(diào)度任務(wù)的?
*2 結(jié)構(gòu)又有哪些?
*3任務(wù)的調(diào)度過程是如何調(diào)度的?

帶著這一系列的問題我們開始探索GCD的函數(shù)與隊(duì)列的搭配使用情況。

二、函數(shù)

在我們測(cè)試的Demo中我們執(zhí)行一個(gè)相關(guān)的GCD同步函數(shù)dispatch_async,同時(shí)向編譯器下一個(gè)符號(hào)斷點(diǎn) 簡單的打印一個(gè)任務(wù)

 dispatch_async(conque, ^{
        NSLog(@"12334");
    });

我們會(huì)看到當(dāng)前的斷點(diǎn)會(huì)定位到系統(tǒng)的libdispatch.dylib dispatch_async:

系統(tǒng)庫定位.png

通過以上我們就知道GCD的源碼在libdispatch.dylib,帶著找到開源的庫。懷著一個(gè)好奇的心去看看具體的GCD相關(guān)函數(shù)是如何實(shí)現(xiàn),底層的調(diào)用機(jī)制又是怎么樣的,接下來讓我們進(jìn)入函數(shù)的探索環(huán)節(jié)吧

2.1 dispatch_sync(同步函數(shù))

我們進(jìn)去到libdispatch.dylib,進(jìn)行全局的搜索dispatch_sync會(huì)找到相應(yīng)的函數(shù)定義

void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
    uintptr_t dc_flags = DC_FLAG_BLOCK;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    }
    _dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

我們能看到底層是調(diào)用了一個(gè)_dispatch_sync_f函數(shù)來實(shí)現(xiàn)同步函數(shù)的實(shí)現(xiàn)的。我們?cè)俅芜M(jìn)入看看當(dāng)前函數(shù)是如何實(shí)現(xiàn)的

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);
}

我們從源碼就能看到當(dāng)前_dispatch_sync_f是通過封裝一個(gè)叫_dispatch_sync_f_inline內(nèi)聯(lián)函數(shù)從而達(dá)到相關(guān)的同步函數(shù)

_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
        dispatch_function_t func, uintptr_t dc_flags)
{
    if (likely(dq->dq_width == 1)) {
        return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
    }
    
    _dispatch_introspection_sync_begin(dl);
    _dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
            _dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));
}

再次順著相應(yīng)的開源的代碼進(jìn)入到同步函數(shù)的執(zhí)行以及完成函數(shù)_dispatch_sync_invoke_and_complete

_dispatch_sync_invoke_and_complete(dispatch_lane_t dq, void *ctxt,
        dispatch_function_t func DISPATCH_TRACE_ARG(void *dc))
{
    _dispatch_sync_function_invoke_inline(dq, ctxt, func);
    _dispatch_trace_item_complete(dc);
    _dispatch_lane_non_barrier_complete(dq, 0);
}

再次進(jìn)入到_dispatch_sync_function_invoke_inline的我們能看到相應(yīng)的函數(shù)調(diào)用過程

_dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt,
        dispatch_function_t func)
{
    dispatch_thread_frame_s dtf;
    _dispatch_thread_frame_push(&dtf, dq);
    _dispatch_client_callout(ctxt, func);
    _dispatch_perfmon_workitem_inc();
    _dispatch_thread_frame_pop(&dtf);
}

最終到_dispatch_client_callout

_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
    @try {
        return f(ctxt);
    }
    @catch (...) {
        objc_terminate();
    }
}

執(zhí)行步驟如下:

  • 1 先給任務(wù)分配一個(gè)任務(wù)棧
  • 2把當(dāng)前調(diào)度任務(wù)進(jìn)行入棧。讓當(dāng)前線程準(zhǔn)備開始調(diào)度相關(guān)的任務(wù)
  • 3 線程調(diào)度當(dāng)前的任務(wù)
  • 4 執(zhí)行完成后把當(dāng)前任務(wù)彈棧。進(jìn)行相應(yīng)的釋放操作,
總結(jié):同步函數(shù)的執(zhí)行流程是dispatch_async -> _dispatch_sync_f -> _dispatch_sync_f_inline -> _dispatch_sync_invoke_and_complete -> _dispatch_sync_function_invoke_inline -> _dispatch_client_callout -> f(ctxt);

2.2dispatch_async(異步函數(shù))

再次全局搜索2dispatch_async進(jìn)入到相關(guān)的異步函數(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;
    // 任務(wù)包裝器 - 接受 - 保存 - 函數(shù)式
    // 保存 block 
    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}

我們通過代碼知道,異步函數(shù)是通過一個(gè)包裝器進(jìn)行相應(yīng)的任務(wù)包裝,然后進(jìn)行相應(yīng)的函數(shù)執(zhí)行:進(jìn)入到_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);
}

我們知道如果當(dāng)前是GCD的調(diào)用會(huì)走_dispatch_trace_item_push,所以進(jìn)入到源碼一探究竟

_dispatch_trace_item_push(dispatch_queue_class_t dqu, dispatch_object_t _tail)
{
    _dispatch_trace_item_push_inline(dqu._dq, _tail._do);
    _dispatch_introspection_queue_push(dqu, _tail);
}

順著源碼進(jìn)入第一個(gè)函數(shù)去初探,我們能看到是一個(gè)任務(wù)隊(duì)列封裝器,目的就是通過賦值和相應(yīng)的操作以返回一個(gè)全新的任務(wù)隊(duì)列。有興趣的朋友可以自行去研究了一下;而實(shí)際調(diào)用過程是第二個(gè)函數(shù)_dispatch_introspection_queue_push

static inline void
_dispatch_introspection_queue_push(dispatch_queue_class_t dqu,
        dispatch_object_t dou)
{
    _dispatch_introspection_queue_item_enqueue(dqu, dou);
}

再次進(jìn)入底層_dispatch_introspection_queue_item_enqueue

_dispatch_introspection_queue_item_enqueue(dispatch_queue_t dq,
        dispatch_object_t dou)
{
    DISPATCH_INTROSPECTION_INTERPOSABLE_HOOK_CALLOUT(
            queue_item_enqueue, dq, dou);
    if (DISPATCH_INTROSPECTION_HOOK_ENABLED(queue_item_enqueue)) {
        _dispatch_introspection_queue_item_enqueue_hook(dq, dou);
    }
}

到最終我們看到調(diào)用的關(guān)鍵字段_dispatch_introspection_queue_item_enqueue_hook

_dispatch_introspection_queue_item_enqueue_hook(dispatch_queue_t dq,
        dispatch_object_t dou)
{
    dispatch_introspection_queue_item_s diqi;
    diqi = dispatch_introspection_queue_item_get_info(dq, dou._dc);
    dispatch_introspection_hook_callout_queue_item_enqueue(dq, &diqi);
}

我們看到最終異步函數(shù)的調(diào)用是通過封裝的宏定義函數(shù)執(zhí)行的

#define DISPATCH_INTROSPECTION_HOOK_CALLOUT(h, ...) ({ \
        __typeof__(_dispatch_introspection_hooks.h) _h; \
        _h = _dispatch_introspection_hooks.h; \
        if (unlikely((void*)(_h) != DISPATCH_INTROSPECTION_NO_HOOK)) { \
            _h(__VA_ARGS__); \
        } })
總結(jié):異步函數(shù)的執(zhí)行流程是 dispatch_async -> _dispatch_continuation_async -> _dispatch_trace_item_push -> _dispatch_introspection_queue_push -> _dispatch_introspection_queue_item_enqueue -> _dispatch_introspection_queue_item_enqueue_hook -> DISPATCH_INTROSPECTION_HOOK_CALLOUT

三、隊(duì)列

我們都知道在iOS開發(fā)過程當(dāng)中或者是任何一門操作系統(tǒng)語言中,隊(duì)列都是一個(gè)很重要的數(shù)據(jù)結(jié)構(gòu),它有著FIFO(先進(jìn)先出)原則,根據(jù)操作系統(tǒng)內(nèi)核的不同,隊(duì)列又劃分為串行隊(duì)列并發(fā)隊(duì)列。
接下來就讓我們從不同的地方取分析這兩種隊(duì)列。

3.1 串行隊(duì)列

串行隊(duì)列的概念

所有的調(diào)度任務(wù)進(jìn)入到任務(wù)棧以后,就由CPU統(tǒng)一調(diào)度,最先進(jìn)去的任務(wù)先調(diào)度,在調(diào)度任務(wù)未完成之前,其他任務(wù)不能被調(diào)度。這就是串行隊(duì)列,也就是相當(dāng)于排隊(duì)買票,一個(gè)一個(gè)來的進(jìn)行。

串行隊(duì)列結(jié)構(gòu).png
串行隊(duì)列的結(jié)構(gòu)

我們?cè)跍y(cè)試Demo中創(chuàng)建iOS開發(fā)過程中的常用幾種隊(duì)列,mainQueue,serialQueue,globalQueue,以及concurrentQueue, 并且答應(yīng)相關(guān)類的結(jié)構(gòu)可知

 dispatch_queue_t serial = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
    // OS_dispatch_queue_concurrent
    dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    // DISPATCH_QUEUE_SERIAL max && 1
    // queue 對(duì)象 alloc init class
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    // 多個(gè) - 集合
    dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
    
    NSLog(@"%@-%@-%@-%@",serial,conque,mainQueue,globQueue);

打印結(jié)果如下

<OS_dispatch_queue_serial: cooci>-<OS_dispatch_queue_concurrent: cooci>-<OS_dispatch_queue_main: com.apple.main-thread>-<OS_dispatch_queue_global: com.apple.root.default-qos>

所以從上我們知道了主隊(duì)列也是串行隊(duì)列,只是不同于一般的串行隊(duì)列而已,從iOS GCD文檔中已經(jīng)標(biāo)記的很明白了。那么串行隊(duì)列的底層源碼是如何實(shí)現(xiàn)的呢?我們繼續(xù)探索

我們從以上的打印結(jié)果中知道,主隊(duì)列的打印結(jié)果是OS_dispatch_queue_main: com.apple.main-thread,我們進(jìn)入源碼搜索主線程的結(jié)果 能看到相應(yīng)的隊(duì)列定義

struct dispatch_queue_static_s _dispatch_main_q = {
    DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
    .do_targetq = _dispatch_get_default_queue(true),
#endif
    .dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
            DISPATCH_QUEUE_ROLE_BASE_ANON,
    .dq_label = "com.apple.main-thread",
    .dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
    .dq_serialnum = 1,
};

3.2 并發(fā)隊(duì)列

并發(fā)隊(duì)列的概念:

所有的調(diào)度任務(wù)進(jìn)入到任務(wù)棧以后,就由CPU統(tǒng)一調(diào)度,最先進(jìn)去的任務(wù)先調(diào)度,但是由于計(jì)算機(jī)的CPU 在短暫的時(shí)間內(nèi)可以對(duì)多個(gè)調(diào)度任務(wù)進(jìn)行處理,利用時(shí)間片輪轉(zhuǎn)發(fā)來進(jìn)行任務(wù)的調(diào)度,所以就好像多個(gè)調(diào)度任務(wù)同時(shí)執(zhí)行的意思;

并發(fā)隊(duì)列結(jié)構(gòu).png
并發(fā)隊(duì)列的結(jié)構(gòu)

我們從以上的打印結(jié)果中知道,主隊(duì)列的打印結(jié)果是OS_dispatch_queue_concurrent,我們進(jìn)入源碼搜索主線程的結(jié)果 能看到相應(yīng)的隊(duì)列定義

struct dispatch_queue_global_s _dispatch_root_queues[] = {
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
        .dq_label = "com.apple.root.default-qos",
        .dq_serialnum = 10,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
            DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.default-qos.overcommit",
        .dq_serialnum = 11,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
        .dq_label = "com.apple.root.user-initiated-qos",
        .dq_serialnum = 12,
    ),

3.3 隊(duì)列如何創(chuàng)建的并且關(guān)聯(lián)類信息

我們?cè)诖蛴〉慕Y(jié)果中已經(jīng)知道串行隊(duì)列打印的結(jié)構(gòu)是OS_dispatch_queue_serial ,并發(fā)隊(duì)列打印的結(jié)構(gòu)是OS_dispatch_queue_concurrent,接下來就讓我們進(jìn)入源碼看看隊(duì)列是如何關(guān)聯(lián)類對(duì)象并且isa指針的,
首先我們進(jìn)入到dispatch_queue_create函數(shù)看看源碼

dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
    return _dispatch_lane_create_with_target(label, attr,
            DISPATCH_TARGET_QUEUE_DEFAULT, true);
}

再次進(jìn)入到_dispatch_lane_create_with_target

static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
        dispatch_queue_t tq, bool legacy)
{
    // dqai 創(chuàng)建 -
    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

    //
    // Step 1: Normalize arguments (qos, overcommit, tq)
    //
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
            sizeof(struct dispatch_lane_s)); // alloc
    _dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
            DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
            (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // init

    dq->dq_label = label;
    dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
            dqai.dqai_relpri);
    if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
        dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
    }
    if (!dqai.dqai_inactive) {
        _dispatch_queue_priority_inherit_from_target(dq, tq);
        _dispatch_lane_inherit_wlh_from_target(dq, tq);
    }
    _dispatch_retain(tq);
    dq->do_targetq = tq;
    _dispatch_object_debug(dq, "%s", __func__);
    return _dispatch_trace_queue_create(dq)._dq;

就這樣能進(jìn)行相關(guān)的隊(duì)列的創(chuàng)建過程;

以上創(chuàng)建過程中如果需要進(jìn)行相應(yīng)的串行和并發(fā)的判斷

const void *vtable;
    dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
    if (dqai.dqai_concurrent) {
        // OS_dispatch_queue_concurrent
        vtable = DISPATCH_VTABLE(queue_concurrent);
    } else {
        vtable = DISPATCH_VTABLE(queue_serial);
    }

然后我們進(jìn)入到DISPATCH_VTABLE中去看看是如何決定一個(gè)隊(duì)列是串行還是并發(fā)的

我們能找到相應(yīng)的宏定義是

define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)

再次搜索DISPATCH_OBJC_CLASS

我們也知道當(dāng)前是一個(gè)宏定義如下

#define OS_OBJECT_VTABLE(name)      (&OS_OBJECT_CLASS_SYMBOL(name))
#define DISPATCH_OBJC_CLASS(name)   (&DISPATCH_CLASS_SYMBOL(name))

我們?cè)俅稳炙阉?code>DISPATCH_CLASS_SYMBOL就能找到定義如下

#define OS_OBJECT_EXTRA_VTABLE_SYMBOL(name) _OS_##name##_vtable
#define DISPATCH_CLASS_SYMBOL(name) OS_dispatch_##name##_class

從以上的源碼定義我們就知道系統(tǒng)是通過拼接名字進(jìn)行的

通過以上創(chuàng)建的類進(jìn)行名字拼接就得到我們自己所定義的類。這就是隊(duì)列創(chuàng)建和關(guān)聯(lián)的過程。

四、總結(jié)

以上就是本人對(duì)隊(duì)列的創(chuàng)建和函數(shù)底層源碼的調(diào)用過程的學(xué)習(xí),由于libdispatch源碼晦澀難懂,所以我只能跟著源碼一步步查詢和學(xué)習(xí),有很多不足之處,請(qǐng)大聲多多指教。

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

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