深入理解GCD之dispatch_queue

原文鏈接深入理解GCD之dispatch_queue

更新于2020-12-13 更新異步執(zhí)行任務(wù)的源碼分析

GCD隊列是我們在使用GCD中經(jīng)常接觸的技術(shù)點睁冬,分析dispatch_queue部分的源碼能更好得理解多線程時的處理朴上。但是libdispatch的源碼相對來說比較復(fù)雜,綜合考慮下控乾,使用了libdispatch-187.9進行分析荣堰。

1. 隊列和線程的關(guān)系

Concurrent Programming: APIs and Challenges中的一張圖片可以很直觀地描述GCD與線程之間的關(guān)系:

GCDandThread@2x

線程和隊列并不是一對一的關(guān)系葬燎,一個線程內(nèi)可能有多個隊列辫呻,這些隊列可能是串行的或者是并行的埋同,按照同步或者異步的方式工作劳较。

對于主線程和主隊列來說驹止,主隊列是主線程上的一個串行隊列,是系統(tǒng)自動為我們創(chuàng)建的观蜗,換言之臊恋,主線程是可以執(zhí)行除主隊列之外其他隊列的任務(wù)。我們可以用下面一段代碼進行測試:

// 測試代碼
override func viewDidLoad() {
    super.viewDidLoad()
    
    let sQueue = DispatchQueue(label: "sQueue")
    // 串行隊列同步不會產(chǎn)生新線程墓捻,任務(wù)在當(dāng)前線程下執(zhí)行抖仅,因此Thread.current必然是主線程
    sQueue.sync { print(Thread.current) }
}

2. 隊列的定義

2.1 dispatch_queue_s

dispatch_queue_s是隊列的結(jié)構(gòu)體,可以說我們在GCD中接觸最多的結(jié)構(gòu)體了砖第。GCD中使用了很多的宏撤卢,不利于我們理解代碼,我們用對應(yīng)的結(jié)構(gòu)替換掉定義的宏梧兼。

為了方便后續(xù)的分析放吩,先列出一些函數(shù)方便后面的理解

struct dispatch_queue_s {
    // 第一部分:DISPATCH_STRUCT_HEADER(dispatch_queue_s, dispatch_queue_vtable_s)
    const struct dispatch_queue_vtable_s *do_vtable; // 該類型的結(jié)構(gòu)體包含了對dispatch_queue_s的操作函數(shù)
    struct dispatch_queue_s *volatile do_next; //鏈表的next
    unsigned int do_ref_cnt; // 引用計數(shù)
    unsigned int do_xref_cnt; // 外部引用計數(shù)
    unsigned int do_suspend_cnt; // 暫停標(biāo)志,比如延時處理中羽杰,在任務(wù)到時后渡紫,計時器處理將會將該標(biāo)志位修改,然后喚醒隊列調(diào)度
    struct dispatch_queue_s *do_targetq; // 目標(biāo)隊列考赛,GCD允許我們將一個隊列放在另一個隊列里執(zhí)行任務(wù)
    void *do_ctxt; // 上下文惕澎,用來存儲線程池相關(guān)數(shù)據(jù),比如用于線程掛起和喚醒的信號量颜骤、線程池尺寸等
    void *do_finalizer;
    
    // 第二部分:DISPATCH_QUEUE_HEADER
    uint32_t volatile dq_running; // 隊列運行的任務(wù)數(shù)量
    uint32_t dq_width; // 最大并發(fā)數(shù):主隊列/串行隊列的最大并發(fā)數(shù)為1
    struct dispatch_object_s *volatile dq_items_tail; // 隊列尾結(jié)點
    struct dispatch_object_s *volatile dq_items_head; // 隊列頭結(jié)點
    unsigned long dq_serialnum; // 隊列序列號
    dispatch_queue_t dq_specific_q; // specific隊列
    
    char dq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE]; // 隊列名唧喉,隊列名要少于64個字符    
    char _dq_pad[DISPATCH_QUEUE_CACHELINE_PAD]; // for static queues only
};

2.2 dispatch_queue_vtable_s

在GCD隊列中,dispatch_queue_vtable_s這個結(jié)構(gòu)體內(nèi)包含了dispatch_object_s的操作函數(shù),而且針對這些操作函數(shù)欣喧,定義了相對簡短的宏腌零,方便調(diào)用。

// dispatch_queue_vtable_s結(jié)構(gòu)體唆阿,聲明了一些函數(shù)用于操作dispatch_queue_s結(jié)構(gòu)體
struct dispatch_queue_vtable_s {
    // DISPATCH_VTABLE_HEADER(dispatch_queue_s);
    unsigned long const do_type;
    const char *const do_kind;
    size_t (*const do_debug)(struct dispatch_queue_s *, char *, size_t);
    // 喚醒隊列的方法益涧,全局隊列和主隊列此項為NULL
    struct dispatch_queue_s *(*const do_invoke)(struct dispatch_queue_s); 
    // 用于檢測傳入對象中的一些值是否滿足條件
    bool (*const do_probe)(struct dispatch_queue_s *);
    // 銷毀隊列的方法,通常內(nèi)部會調(diào)用這個對象的finalizer函數(shù)
    void (*const do_dispose)(struct dispatch_queue_s *)
};

在queue.c中定義三個關(guān)于dispatch_queue_vtable_s的靜態(tài)常量驯鳖,分別是

// 用于主隊列和自定義隊列
const struct dispatch_queue_vtable_s _dispatch_queue_vtable = {
    .do_type = DISPATCH_QUEUE_TYPE,
    .do_kind = "queue",
    .do_dispose = _dispatch_queue_dispose,
    .do_invoke = NULL,
    .do_probe = (void *)dummy_function_r0,
    .do_debug = dispatch_queue_debug,
};

// 用于全局隊列
static const struct dispatch_queue_vtable_s _dispatch_queue_root_vtable = {
    .do_type = DISPATCH_QUEUE_GLOBAL_TYPE,
    .do_kind = "global-queue",
    .do_debug = dispatch_queue_debug,
    .do_probe = _dispatch_queue_wakeup_global,
};

// 用于管理隊列
static const struct dispatch_queue_vtable_s _dispatch_queue_mgr_vtable = {
    .do_type = DISPATCH_QUEUE_MGR_TYPE,
    .do_kind = "mgr-queue",
    .do_invoke = _dispatch_mgr_thread,
    .do_debug = dispatch_queue_debug,
    .do_probe = _dispatch_mgr_wakeup,
};

3. 隊列的類型

隊列的類型可以分為主隊列闲询、管理隊列自定義隊列浅辙、全局隊列4種類型扭弧。

3.1 主隊列

使用dispatch_get_main_queue()可獲取主隊列,它的定義如下:

#define dispatch_get_main_queue() (&_dispatch_main_q)

struct dispatch_queue_s _dispatch_main_q = {
#if !DISPATCH_USE_RESOLVERS
    .do_vtable = &_dispatch_queue_vtable,
    .do_targetq = &_dispatch_root_queues[
            DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY], // root queue中的其中一個
#endif
    .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
    .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
    .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
    .dq_label = "com.apple.main-thread",
    .dq_running = 1,
    .dq_width = 1, // 說明主隊列是一個串行隊列
    .dq_serialnum = 1, // 主隊列序列號
};

do_vtable

主隊列的do_vtable_dispatch_queue_vtable记舆。

do_targetq

do_targetq即目標(biāo)隊列鸽捻,關(guān)于目標(biāo)隊列的意義,在分析全局隊列的do_targetq中會給一個比較具體的總結(jié)泽腮。

主隊列的目標(biāo)隊列定義:

[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[
                DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY],
        .dq_label = "com.apple.root.default-overcommit-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 7,
}

do_ref_cnt御蒲、do_xref_cnt

do_ref_cntdo_xref_cnt是引用計數(shù),它們和GCD對象的內(nèi)存管理相關(guān)诊赊。主隊列的這兩個值為DISPATCH_OBJECT_GLOBAL_REFCNT厚满。

void dispatch_retain(dispatch_object_t dou) {
    if (slowpath(dou._do->do_xref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT)) {
        return; // global object
    }
    // ...
}

void _dispatch_retain(dispatch_object_t dou) {
    if (slowpath(dou._do->do_ref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT)) {
        return; // global object
    }
    // ...
}

void dispatch_release(dispatch_object_t dou) {
    if (slowpath(dou._do->do_xref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT)) {
        return;
    }
    // 調(diào)用_dispatch_release函數(shù);
    // ...
}

void _dispatch_release(dispatch_object_t dou) {
    if (slowpath(dou._do->do_ref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT)) {
        return; // global object
    }
    // 調(diào)用dx_dispose宏即調(diào)用do_dispose
    // ...
}

從上面這幾個函數(shù)可以看出:

  • 主隊列的生命周期是伴隨著應(yīng)用的,不會受retain和release的影響碧磅。
  • 當(dāng)do_ref_cnt碘箍、do_xref_cnt這兩個值同時為0的時候,對象才會被釋放鲸郊。

3.2 管理隊列

管理隊列是GCD的內(nèi)部隊列丰榴,不對外公開,這個隊列應(yīng)該是用來扮演管理的角色秆撮,GCD定時器就用到了管理隊列四濒。

struct dispatch_queue_s _dispatch_mgr_q = {
    .do_vtable = &_dispatch_queue_mgr_vtable,
    .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
    .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
    .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
    .do_targetq = &_dispatch_root_queues[
            DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY],
    .dq_label = "com.apple.libdispatch-manager",
    .dq_width = 1,
    .dq_serialnum = 2, // 管理隊列序列號
};

do_vtable

管理隊列的do_vtable_dispatch_queue_mgr_vtable

do_targetq

管理隊列的目標(biāo)隊列:

[DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[
                DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY],
        .dq_label = "com.apple.root.high-overcommit-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 9,
}

do_ref_cnt像吻、do_xref_cnt

管理隊列的這兩個值為DISPATCH_OBJECT_GLOBAL_REFCNT峻黍,所以和主隊列的生命周期應(yīng)該是一樣的。

3.3 自定義隊列

使用dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)創(chuàng)建一個自定義的隊列拨匆。它的源碼如下:

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr) {
    dispatch_queue_t dq;
    size_t label_len;
    
    if (!label) {
        label = "";
    }
    
    label_len = strlen(label);
    if (label_len < (DISPATCH_QUEUE_MIN_LABEL_SIZE - 1)) {
        label_len = (DISPATCH_QUEUE_MIN_LABEL_SIZE - 1);
    }
    
    // XXX switch to malloc()
    dq = calloc(1ul, sizeof(struct dispatch_queue_s) -
                DISPATCH_QUEUE_MIN_LABEL_SIZE - DISPATCH_QUEUE_CACHELINE_PAD +
                label_len + 1);
    if (slowpath(!dq)) {
        return dq;
    }
    
    // _dispatch_queue_init(dq);
    // 隊列初始化展開如下
    dq->do_vtable = &_dispatch_queue_vtable;
    dq->do_next = DISPATCH_OBJECT_LISTLESS;
    dq->do_ref_cnt = 1;
    dq->do_xref_cnt = 1;
    // Default target queue is overcommit!
    // 使用的目標(biāo)隊列:_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY]
    dq->do_targetq = _dispatch_get_root_queue(0, true);
    dq->dq_running = 0;
    dq->dq_width = 1;
    dq->dq_serialnum = dispatch_atomic_inc(&_dispatch_queue_serial_numbers) - 1;
    
    strcpy(dq->dq_label, label);
    
    if (fastpath(!attr)) {
        return dq;
    }
    // 如果是并發(fā)隊列姆涩,設(shè)置最大并發(fā)數(shù),UINT32_MAX可以看成不限制最大并發(fā)數(shù)
    if (fastpath(attr == DISPATCH_QUEUE_CONCURRENT)) {
        dq->dq_width = UINT32_MAX;
        // 設(shè)置目標(biāo)隊列惭每,對于并發(fā)隊列_dispatch_get_root_queue函數(shù)中的overcommit傳的是false骨饿,獲取的值: _dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY]
        dq->do_targetq = _dispatch_get_root_queue(0, false);
    } else {
        dispatch_debug_assert(!attr, "Invalid attribute");
    }
    return dq;
}

slowpath(x)亏栈、fastpath(x)

#define fastpath(x) ((typeof(x))__builtin_expect((long)(x), ~0l)) // ~0l就是1
#define slowpath(x) ((typeof(x))__builtin_expect((long)(x), 0l))

fastpath(x)表示x的值極大概率為1唉铜,即多數(shù)情況下會發(fā)生颅崩。slowpath(x)表示x的值極大概率為0,即多數(shù)情況下不會發(fā)生灸芳。
__builtin_expect來幫助程序員處理分支預(yù)測察署,優(yōu)化程序闷游,這個函數(shù)的語義是:我期望表達式的值等于常量C,編譯器應(yīng)當(dāng)根據(jù)我提供的期望值進行優(yōu)化贴汪。

do_vtable

與主隊列一樣脐往,自定義隊列的do_vtable也是_dispatch_queue_vtable

do_targetq

自定義隊列的目標(biāo)隊列有兩種:

  1. 如果是串行隊列扳埂,則使用_dispatch_get_root_queue(0, true)函數(shù)獲取目標(biāo)隊列业簿,獲取到的目標(biāo)隊列是_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY]
  2. 如果是并發(fā)隊列阳懂,則使用_dispatch_get_root_queue(0, false)函數(shù)獲取目標(biāo)隊列梅尤,獲取到的目標(biāo)隊列是_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY]

_dispatch_get_root_queue(long priority, bool overcommit)函數(shù)的overcommit參數(shù)代表隊列在執(zhí)行block時岩调,無論系統(tǒng)多忙都會新開一個線程巷燥,這樣做的目的是不會造成某個線程過載。

dq_serialnum

dq_serialnum是在_dispatch_queue_serial_numbers基礎(chǔ)上進行原子操作加1誊辉,即從12開始累加矾湃。1到11被保留的序列號定義如下:

// skip zero
// 1 - main_q
// 2 - mgr_q
// 3 - _unused_
// 4,5,6,7,8,9,10,11 - global queues
// we use 'xadd' on Intel, so the initial value == next assigned

其中1用于主隊列亡脑,2用于管理隊列堕澄,3暫時沒有被使用,4~11是用于全局隊列的霉咨。由于看的源碼版本比較老了蛙紫,后面蘋果有新增了幾個隊列。

3.4 全局隊列

上面說了很多全局隊列途戒,現(xiàn)在我們來看一下全局隊列是如何定義的坑傅。

dispatch_queue_t dispatch_get_global_queue(long priority, unsigned long flags) {
    if (flags & ~DISPATCH_QUEUE_OVERCOMMIT) {
        return NULL;
    }
    return _dispatch_get_root_queue(priority, flags & DISPATCH_QUEUE_OVERCOMMIT);
}

static inline dispatch_queue_t _dispatch_get_root_queue(long priority, bool overcommit) {
    if (overcommit) switch (priority) {
    case DISPATCH_QUEUE_PRIORITY_LOW:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_DEFAULT:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_HIGH:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_BACKGROUND:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY];
    }
    
    switch (priority) {
    case DISPATCH_QUEUE_PRIORITY_LOW:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_DEFAULT:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_HIGH:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY];
    case DISPATCH_QUEUE_PRIORITY_BACKGROUND:
        return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY];
    default:
        return NULL;
    }
}

struct dispatch_queue_s _dispatch_root_queues[] = {
    [DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[
                DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY],

        .dq_label = "com.apple.root.low-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 4,
    },
    [DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[
                DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY],

        .dq_label = "com.apple.root.low-overcommit-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 5,
    },
    [DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[
                DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY],

        .dq_label = "com.apple.root.default-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 6,
    },
    [DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[
                DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY],

        .dq_label = "com.apple.root.default-overcommit-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 7,
    },
    [DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[
                DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY],

        .dq_label = "com.apple.root.high-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 8,
    },
    [DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[
                DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY],

        .dq_label = "com.apple.root.high-overcommit-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 9,
    },
    [DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[
                DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY],

        .dq_label = "com.apple.root.background-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 10,
    },
    [DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY] = {
        .do_vtable = &_dispatch_queue_root_vtable,
        .do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
        .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
        .do_ctxt = &_dispatch_root_queue_contexts[
                DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY],

        .dq_label = "com.apple.root.background-overcommit-priority",
        .dq_running = 2,
        .dq_width = UINT32_MAX,
        .dq_serialnum = 11,
    },
};

do_vtable

全局隊列的do_vtable_dispatch_queue_root_vtable。前面提到_dispatch_queue_root_vtable的檢測函數(shù)(do_probe)為_dispatch_queue_wakeup_global喷斋,這個函數(shù)用來喚醒全局隊列唁毒,具體的后面分析隊列喚醒的時候再講。

do_targetq

無論是主隊列星爪、管理隊列還是自定義隊列浆西,它們都使用了全局隊列(就是從root queue中獲取的)作為目標(biāo)隊列,但是全局隊列并沒有設(shè)置do_targetq顽腾。

Concurrent Programming: APIs and Challenges提到:

While custom queues are a powerful abstraction, all blocks you schedule on them will ultimately trickle down to one of the system’s global queues and its thread pool(s).

雖然自定義隊列是一個強大的抽象近零,但你在隊列上安排的所有Block最終都會落到系統(tǒng)的某一個全局隊列及其線程池中。那也就是說GCD用到的queue,無論是自定義隊列久信,或是獲取系統(tǒng)的主隊列窖杀、全局隊列、管理隊列裙士,其最終都是落腳于GCD root queue中入客。GCD管理的也不過這些root queue。

do_ref_cnt腿椎、do_xref_cnt

管理隊列的這兩個值為DISPATCH_OBJECT_GLOBAL_REFCNT痊项,所以和主隊列的生命周期應(yīng)該是一樣的。

do_ctxt

全局隊列中有一個上下文的屬性酥诽,用來存儲線程池相關(guān)數(shù)據(jù)鞍泉,比如用于線程掛起和喚醒的信號量、線程池尺寸等肮帐。它的定義如下:

static struct dispatch_root_queue_context_s _dispatch_root_queue_contexts[] = {
    [DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY] = {
#if DISPATCH_ENABLE_THREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    },
    [DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY] = {
#if DISPATCH_ENABLE_THREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    },
    [DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY] = {
#if DISPATCH_ENABLE_THREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    },
    [DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY] = {
#if DISPATCH_ENABLE_THREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    },
    [DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY] = {
#if DISPATCH_ENABLE_THREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    },
    [DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY] = {
#if DISPATCH_ENABLE_THREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    },
    [DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY] = {
#if DISPATCH_ENABLE_THREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    },
    [DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY] = {
#if DISPATCH_ENABLE_THREAD_POOL
        .dgq_thread_mediator = &_dispatch_thread_mediator[
                DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY],
        .dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
    },
};

4. 隊列的同步:dispatch_sync分析

測試代碼

// 串行隊列
let sQueue = DispatchQueue(label: "sQueue")
print(1)
sQueue.sync { print("\(2):\(Thread.current)") }
print(3)
sQueue.sync { print("\(4):\(Thread.current)") }
print(5)

// 并行隊列
let cQueue = DispatchQueue(label: "cQueue", attributes: [.concurrent])
print(1)
cQueue.sync { print("\(2):\(Thread.current)") }
print(3)
cQueue.sync { print("\(4):\(Thread.current)") }
print(5)

執(zhí)行結(jié)果

1
2:<NSThread: 0x600002478980>{number = 1, name = main}
3
4:<NSThread: 0x600002478980>{number = 1, name = main}
5
1
2:<NSThread: 0x600002478980>{number = 1, name = main}
3
4:<NSThread: 0x600002478980>{number = 1, name = main}
5

雖然省略主隊列和全局隊列的測試咖驮,但是結(jié)果是一樣的。隊列同步執(zhí)行任務(wù)的過程训枢,是不會開辟新的線程托修,所有任務(wù)在當(dāng)前線程中執(zhí)行,且會阻塞線程恒界。

4.1 入口函數(shù):dispatch_sync

dispatch_sync的源碼如下:

void dispatch_sync(dispatch_queue_t dq, void (^work)(void)) {
    // DISPATCH_COCOA_COMPAT是Mac OS下才會走的
#if DISPATCH_COCOA_COMPAT
    // 是否是主隊列
    if (slowpath(dq == &_dispatch_main_q)) {
        // 內(nèi)部也是執(zhí)行dispatch_sync_f函數(shù)
        return _dispatch_sync_slow(dq, work);
    }
#endif
    struct Block_basic *bb = (void *)work;
    dispatch_sync_f(dq, work, (dispatch_function_t)bb->Block_invoke);
}
#endif

_dispatch_sync_slow函數(shù)內(nèi)部也是執(zhí)行dispatch_sync_f函數(shù)睦刃,所以dispatch_sync的調(diào)用本質(zhì)即dispatch_sync_f函數(shù)。

void dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
    // 串行隊列包括主隊列
    if (fastpath(dq->dq_width == 1)) {
        return dispatch_barrier_sync_f(dq, ctxt, func);
    }
    // 全局隊列十酣,全局隊列是沒有do_targetq的涩拙,主隊列/管理隊列/自定義隊列都有
    if (slowpath(!dq->do_targetq)) {
        // the global root queues do not need strict ordering
        (void)dispatch_atomic_add2o(dq, dq_running, 2);
        return _dispatch_sync_f_invoke(dq, ctxt, func);
    }
    // 其他隊列
    _dispatch_sync_f2(dq, ctxt, func);
}

同步執(zhí)行任務(wù)的時候分成了三種情況:

  1. 如果是串行隊列,執(zhí)行dispatch_barrier_sync_f即柵欄同步函數(shù)耸采;
  2. 如果是全局隊列兴泥,執(zhí)行_dispatch_sync_f_invoke
  3. 如果是其他隊列虾宇,執(zhí)行_dispatch_sync_f2搓彻。

4.2 重點函數(shù):dispatch_barrier_sync_f

在分析dispatch_barrier_sync_f這個函數(shù)前,我們看一下dispatch_barrier_sync函數(shù)即同步柵欄函數(shù)嘱朽。它的實現(xiàn)如下:

void dispatch_barrier_sync(dispatch_queue_t dq, void (^work)(void)) {
#if DISPATCH_COCOA_COMPAT
    if (slowpath(dq == &_dispatch_main_q)) {
       // 內(nèi)部調(diào)用dispatch_barrier_sync_f函數(shù)
        return _dispatch_barrier_sync_slow(dq, work);
    }
#endif
    struct Block_basic *bb = (void *)work;
    dispatch_barrier_sync_f(dq, work, (dispatch_function_t)bb->Block_invoke);
}

它的底層也是調(diào)用dispatch_barrier_sync_f函數(shù)旭贬。如果是串行隊列壓入同步任務(wù),那么當(dāng)前任務(wù)就必須等待前面的任務(wù)執(zhí)行完成后才能執(zhí)行搪泳,源代碼就會調(diào)用dispatch_barrier_sync_f函數(shù)完成上面的效果稀轨。

DISPATCH_NOINLINE
void
dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
    // 1) ensure that this thread hasn't enqueued anything ahead of this call
    // 2) the queue is not suspended
    // 1) 確保此線程在此調(diào)用之前沒有入隊
    // 2) 隊列未掛起
    
    // 第1步:如果串行隊列中存在其他任務(wù)或者隊列被掛起,進入_dispatch_barrier_sync_f_slow森书,
    // 等待這個隊列中的其他任務(wù)完成(用信號量的方式通知)靶端,然后執(zhí)行這個任務(wù)谎势。
    // 多數(shù)情況下不會發(fā)生
    if (slowpath(dq->dq_items_tail) || slowpath(DISPATCH_OBJECT_SUSPENDED(dq))){
        return _dispatch_barrier_sync_f_slow(dq, ctxt, func);
    }
    
    // 第2步:檢查隊列的dq_running狀態(tài),如果沒有運行杨名,進入_dispatch_barrier_sync_f_slow脏榆,等待激活。
    // bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)
    // 比較*ptr與oldval的值台谍,如果兩者相等须喂,則將newval更新到*ptr并返回true
    // dispatch_atomic_cmpxchg2o(dq, dq_running, 0, 1)相當(dāng)于dq->dq_running為0的時候?qū)?    // dq->dq_running設(shè)置為1,并返回true
    if (slowpath(!dispatch_atomic_cmpxchg2o(dq, dq_running, 0, 1))) {
        // global queues and main queue bound to main thread always falls into
        // the slow case
        // 全局隊列和綁定到主線程的主隊列始終屬于慢速情況即會進入_dispatch_barrier_sync_f_slow函數(shù)
        return _dispatch_barrier_sync_f_slow(dq, ctxt, func);
    }
    
    // 第3步:有多重隊列趁蕊,尋找真正的目標(biāo)隊列坞生,其實還是回到了dispatch_sync_f方法
    if (slowpath(dq->do_targetq->do_targetq)) {
        return _dispatch_barrier_sync_f_recurse(dq, ctxt, func);
    }
    
    // 第4步:隊列無任務(wù)執(zhí)行,調(diào)用_dispatch_barrier_sync_f_invoke執(zhí)行任務(wù)掷伙。
    // 內(nèi)部調(diào)用_dispatch_function_invoke去執(zhí)行任務(wù)
    _dispatch_barrier_sync_f_invoke(dq, ctxt, func);
}

這里涉及到三個函數(shù):

  1. _dispatch_barrier_sync_f_slow函數(shù)內(nèi)部使用了線程對應(yīng)的信號量并且調(diào)用wait 方法
  2. _dispatch_barrier_sync_f_recurse函數(shù)內(nèi)部調(diào)用了dispatch_sync_f函數(shù)是己,還是在尋找真正的目標(biāo)隊列
  3. 如果隊列無任務(wù)執(zhí)行,調(diào)用_dispatch_barrier_sync_f_invoke執(zhí)行任務(wù)任柜。執(zhí)行任務(wù)的時候會調(diào)用_dispatch_function_invoke函數(shù)卒废。

4.3 _dispatch_barrier_sync_f_invoke

DISPATCH_NOINLINE
static void
_dispatch_barrier_sync_f_invoke(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
    // _dispatch_function_invoke的實現(xiàn)
    // 將當(dāng)前線程的dispatch_queue_key設(shè)置為dq,然后執(zhí)行任務(wù)宙地,
    // 執(zhí)行完之后再恢復(fù)到之前的old_dq
    dispatch_queue_t old_dq = _dispatch_thread_getspecific(dispatch_queue_key);
    _dispatch_thread_setspecific(dispatch_queue_key, dq);
    _dispatch_client_callout(ctxt, func);
    _dispatch_workitem_inc();
    _dispatch_thread_setspecific(dispatch_queue_key, old_dq);
    
    // 如果隊列中存在其他任務(wù)摔认,用信號量的方法喚醒,然后繼續(xù)執(zhí)行下一個任務(wù)
    if (slowpath(dq->dq_items_tail)) {
        return _dispatch_barrier_sync_f2(dq);
    }
    
    // dispatch_atomic_dec2o這個宏宅粥,會調(diào)用GCC內(nèi)置的函數(shù) __sync_sub_and_fetch参袱,實現(xiàn)減法的原子性操作。因此這一行的意思是將dq_running的值減1秽梅,然后判斷是否與0相等抹蚀。
    // _dispatch_wakeup為喚醒隊列函數(shù)
    if (slowpath(dispatch_atomic_dec2o(dq, dq_running) == 0)) {
        _dispatch_wakeup(dq);
    }
}

4.4 GCD死鎖

看了上面的代碼注釋后,我們來想一下死鎖是怎么產(chǎn)生的风纠?先看下示例代碼:

#import "DeadLock.h"

@implementation DeadLock

- (instancetype)init {
    if (self = [super init]) {
//        [self _mianQueueDeadLock];
        [self _serialQueueDeadLock];
    }
    
    return self;
}

#pragma mark - Private

- (void)_mianQueueDeadLock {
    dispatch_sync(dispatch_get_main_queue(), ^(void){
        NSLog(@"這里死鎖了");
    });
}

- (void)_serialQueueDeadLock {
    dispatch_queue_t queue1 = dispatch_queue_create("1serialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("2serialQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue1, ^{
        NSLog(@"11111");
        
        dispatch_sync(queue1, ^{ 
            // 如果使用queue2就不會發(fā)生死鎖况鸣,使用queue1就會死鎖
            NSLog(@"22222");
        });
    });
}

@end

_serialQueueDeadLock為例:當(dāng)?shù)谝淮螆?zhí)行串行隊列任務(wù)的時候牢贸,跳到第4步竹观,直接開始執(zhí)行任務(wù),在運行第二個dispatch_sync時候潜索,在任務(wù)里面通過執(zhí)行第1步(隊列在運行)向這個同步隊列中壓入信號量臭增,然后等待信號量,進入死鎖竹习。
_mianQueueDeadLock為例:主隊列則會跳轉(zhuǎn)到第2步進入死鎖誊抛。

4.5 _dispatch_sync_f_invoke

如果當(dāng)前隊列是全局隊列的話,就會調(diào)用_dispatch_sync_f_invoke函數(shù)整陌。

static void
_dispatch_sync_f_invoke(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
    // 執(zhí)行任務(wù)
    _dispatch_function_invoke(dq, ctxt, func);
    // dq->dq_running減2后判斷是否等于0拗窃,是就喚醒隊列
    if (slowpath(dispatch_atomic_sub2o(dq, dq_running, 2) == 0)) {
        _dispatch_wakeup(dq);
    }
}

這個函數(shù)的作用:通過_dispatch_function_invoke函數(shù)執(zhí)行傳入的任務(wù)瞎领,然后根據(jù)dq_running檢測任務(wù)隊列有沒有激活,沒有激活就執(zhí)行激活函數(shù)随夸。關(guān)于激活函數(shù)_dispatch_wakeup(dq)放在隊列的異步中講解九默。

4.6 重點函數(shù):_dispatch_sync_f2

根據(jù)前面講到的,如果是其他隊列宾毒,執(zhí)行_dispatch_sync_f2驼修。這個其他隊列我們可以認(rèn)為就是自定義的并行隊列。

_dispatch_sync_f2(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
    // 1) ensure that this thread hasn't enqueued anything ahead of this call
    // 2) the queue is not suspended
    
    // 第1步:隊列中有其他任務(wù)或者隊列被掛起诈铛,壓入信號量開始等待
    if (slowpath(dq->dq_items_tail) || slowpath(DISPATCH_OBJECT_SUSPENDED(dq))){
        return _dispatch_sync_f_slow(dq, ctxt, func);
    }
    // 第2步:隊列沒有激活乙各,激活隊列后執(zhí)行任務(wù),最終還是調(diào)用了_dispatch_sync_f_slow函數(shù)幢竹,只是多了一個_dispatch_wakeup函數(shù)
    if (slowpath(dispatch_atomic_add2o(dq, dq_running, 2) & 1)) {
        return _dispatch_sync_f_slow2(dq, ctxt, func);
    }
    // 第3步:隊列有多重隊列耳峦,尋找真正的目標(biāo)隊列
    if (slowpath(dq->do_targetq->do_targetq)) {
        return _dispatch_sync_f_recurse(dq, ctxt, func);
    }
    // 第4步:隊列無任務(wù)執(zhí)行,調(diào)用_dispatch_sync_f_invoke執(zhí)行任務(wù)焕毫。
    // 內(nèi)部調(diào)用_dispatch_function_invoke去執(zhí)行任務(wù)
    _dispatch_sync_f_invoke(dq, ctxt, func);
}

這里涉及到三個函數(shù):

  1. _dispatch_sync_f_slow函數(shù)內(nèi)部使用了線程對應(yīng)的信號量并且調(diào)用wait方法妇萄。
  2. _dispatch_sync_f_recurse函數(shù)內(nèi)部調(diào)用了dispatch_sync_f函數(shù),還是在尋找真正的目標(biāo)隊列咬荷。
  3. 如果隊列無任務(wù)執(zhí)行冠句,調(diào)用_dispatch_sync_f_invoke執(zhí)行任務(wù)。執(zhí)行任務(wù)的時候會調(diào)用_dispatch_function_invoke函數(shù)幸乒。

通過上面的代碼懦底,隊列的同步執(zhí)行是順序執(zhí)行的。這種順序執(zhí)行跟操作隊列是串行還是并發(fā)是沒有關(guān)系的罕扎。這些操作按著FIFO的方式進入隊列中聚唐,每一個操作都會被等待執(zhí)行直到前一個操作完成8,造成了這種順序執(zhí)行的現(xiàn)象腔召。

現(xiàn)在我們整理一下隊列同步執(zhí)行的流程杆查,如下圖:

gcd_queue_synchronization

5. 隊列的異步:dispatch_async分析

測試代碼

串行隊列測試

let sQueue = DispatchQueue(label: "sQueue")
print(1)
sQueue.async { print("\(2):\(Thread.current)") }
print(3)
sQueue.async { print("\(4):\(Thread.current)") }
print(5)

執(zhí)行結(jié)果

1
3
5
2:<NSThread: 0x600000b884c0>{number = 4, name = (null)}
4:<NSThread: 0x600000b884c0>{number = 4, name = (null)}

并發(fā)隊列測試

let cQueue = DispatchQueue(label: "cQueue", attributes: [.concurrent])
print(1)
cQueue.async { print("\(2):\(Thread.current)") }
print(3)
cQueue.async { print("\(4):\(Thread.current)") }
print(5)

執(zhí)行結(jié)果

1
3
5
4:<NSThread: 0x600002bc69c0>{number = 6, name = (null)}
2:<NSThread: 0x600002bc84c0>{number = 5, name = (null)}

通過上面的測試代碼我們可以知道:

  1. 隊列異步執(zhí)行任務(wù)的過程中,具備開辟新線程的能力臀蛛。
  2. 非主隊列的串行隊列亲桦,會開辟一個新的線程,不會阻塞當(dāng)前線程浊仆,所有任務(wù)有序執(zhí)行客峭。
  3. 并發(fā)隊列會開辟多個線程,具體線程的個數(shù)有體統(tǒng)決定抡柿。所有任務(wù)是無序執(zhí)行的舔琅。

5.1 入口函數(shù):dispatch_async

dispatch_async的源碼如下:

void dispatch_async(dispatch_queue_t dq, void (^work)(void)) {
    dispatch_async_f(dq, _dispatch_Block_copy(work), _dispatch_call_block_and_release);
}

dispatch_async主要將block從棧copy到堆上,或者增加引用計數(shù)洲劣,保證block在執(zhí)行之前不會被銷毀备蚓,另外_dispatch_call_block_and_release用于銷毀block课蔬。然后調(diào)用dispatch_async_f

dispatch_async_f函數(shù)的實現(xiàn):

void
dispatch_async_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
    dispatch_continuation_t dc;

    // No fastpath/slowpath hint because we simply don't know
    // 串行隊列郊尝,執(zhí)行dispatch_barrier_async_f购笆,其實最后還是執(zhí)行任務(wù)入隊的操作
    if (dq->dq_width == 1) {
        return dispatch_barrier_async_f(dq, ctxt, func);
    }

    // 從線程私有數(shù)據(jù)中獲取一個dispatch_continuation_t的結(jié)構(gòu)體
    dc = fastpath(_dispatch_continuation_alloc_cacheonly());
    if (!dc) {
        return _dispatch_async_f_slow(dq, ctxt, func);
    }

    dc->do_vtable = (void *)DISPATCH_OBJ_ASYNC_BIT;
    dc->dc_func = func;
    dc->dc_ctxt = ctxt;

    // No fastpath/slowpath hint because we simply don't know
    // 有目標(biāo)隊列,調(diào)用_dispatch_async_f2函數(shù)進行轉(zhuǎn)發(fā)虚循。
    if (dq->do_targetq) {
        return _dispatch_async_f2(dq, dc);
    }

    // 全局隊列直接進行入隊操作
    _dispatch_queue_push(dq, dc);
}

從上面的源代碼中我們可以看出dispatch_async_f大致分為三種情況:

  1. 如果是串行隊列同欠,調(diào)用dispatch_barrier_async_f函數(shù);
  2. 其他隊列且有目標(biāo)隊列横缔,調(diào)用_dispatch_async_f2函數(shù)铺遂;
  3. 如果是全局隊列的話,直接調(diào)用_dispatch_queue_push函數(shù)進行入隊操作茎刚。

由于隊列的異步執(zhí)行任務(wù)的過程比較復(fù)雜襟锐,我們用一張圖描述一下dispatch_async_f這個函數(shù)執(zhí)行過程:

dispatch_async_f函數(shù)

雖然上面分三種情況,它們最后執(zhí)行都是_dispatch_queue_push_dispatch_async_f2函數(shù)膛锭。另外_dispatch_async_f2函數(shù)其實也是在進行入隊的操作粮坞。所以dispatch_async_f的本質(zhì)就是執(zhí)行_dispatch_queue_push函數(shù)來任務(wù)入隊。

5.2 dispatch_continuation_t結(jié)構(gòu)體

在看上述過程的源碼時會涉及到dispatch_continuation_t這樣的結(jié)構(gòu)體初狰,這個結(jié)構(gòu)體的作用就是封裝我們傳入的異步block的任務(wù)莫杈。以dispatch_barrier_async_f函數(shù)的實現(xiàn)為例子:

void dispatch_barrier_async_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) {
    dispatch_continuation_t dc;
    // 從線程私有數(shù)據(jù)中獲取一個dispatch_continuation_t的結(jié)構(gòu)體。
    dc = fastpath(_dispatch_continuation_alloc_cacheonly());
    if (!dc) {
        // _dispatch_barrier_async_f_slow內(nèi)部也是在進行入隊操作
        return _dispatch_barrier_async_f_slow(dq, ctxt, func);
    }
    
    // DISPATCH_OBJ_BARRIER_BIT奢入,用于阻塞標(biāo)識
    dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
    // 將_dispatch_call_block_and_release作為func方法
    dc->dc_func = func;
    // 將傳入的block作為上下文
    dc->dc_ctxt = ctxt;
    // 入隊操作
    _dispatch_queue_push(dq, dc);
}

另外還需要注意下dispatch_continuation_tdo_vtable的賦值情況筝闹。

// 串行隊列異步或者使用dispatch_barrier_async函數(shù)會有一個DISPATCH_OBJ_BARRIER_BIT的barrier標(biāo)記
dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);

// not barrier
dc->do_vtable = (void *)DISPATCH_OBJ_ASYNC_BIT;

libdispatch全部標(biāo)識符有四種:

#define DISPATCH_OBJ_ASYNC_BIT      0x1     //異步
#define DISPATCH_OBJ_BARRIER_BIT    0x2     //阻塞
#define DISPATCH_OBJ_GROUP_BIT      0x4     //組
#define DISPATCH_OBJ_SYNC_SLOW_BIT  0x8     //同步慢

從上面我們可以知道串行隊列異步執(zhí)行任務(wù)的時候,通過DISPATCH_OBJ_BARRIER_BIT這個標(biāo)識符實現(xiàn)阻塞等待的腥光。

5.3 任務(wù)入隊:_dispatch_queue_push

_dispatch_queue_push是一個宏定義关顷,它最后會變成執(zhí)行_dispatch_queue_push_list函數(shù)。

#define _dispatch_queue_push(x, y) _dispatch_queue_push_list((x), (y), (y))

#define _dispatch_queue_push_list _dispatch_trace_queue_push_list

_dispatch_trace_queue_push_list

void _dispatch_trace_queue_push_list(dispatch_queue_t dq, dispatch_object_t _head, dispatch_object_t _tail) {
    // 是否可以入隊
    if (slowpath(DISPATCH_QUEUE_PUSH_ENABLED())) {
        struct dispatch_object_s *dou = _head._do;
        do {
            // 主要是對dispatch_continuation_s結(jié)構(gòu)體的處理武福,確保后面的使用议双。
            _dispatch_trace_continuation(dq, dou, DISPATCH_QUEUE_PUSH);
        } while (dou != _tail._do && (dou = dou->do_next));
    }
    
    _dispatch_queue_push_list(dq, _head, _tail);
}

_dispatch_queue_push_list

void _dispatch_queue_push_list(dispatch_queue_t dq, dispatch_object_t _head, dispatch_object_t _tail) {
    struct dispatch_object_s *prev, *head = _head._do, *tail = _tail._do;

    tail->do_next = NULL;
    dispatch_atomic_store_barrier();
    // dispatch_atomic_xchg2o實質(zhì)是調(diào)用((typeof(*(p)))__sync_swap((p), (n))),它的定義是將p設(shè)為n并返回p操作之前的值捉片。
    // dispatch_atomic_xchg2o(dq, dq_items_tail, tail)相當(dāng)于dq->dq_items_tail = tail平痰,重新設(shè)置了隊列的尾指針
    prev = fastpath(dispatch_atomic_xchg2o(dq, dq_items_tail, tail));
    if (prev) {
        // if we crash here with a value less than 0x1000, then we are at a
        // known bug in client code for example, see _dispatch_queue_dispose
        // or _dispatch_atfork_child
        // prev是原先的隊尾,如果隊列中有其他的元素界睁,就將壓入的對象加在隊列的尾部觉增。
        prev->do_next = head;
    } else {
        // 如果隊列為空
        _dispatch_queue_push_list_slow(dq, head);
    }
}

_dispatch_queue_push_list_slow

_dispatch_queue_push_list_slow(dispatch_queue_t dq,
        struct dispatch_object_s *obj)
{
    //dq->dq_items_head設(shè)置為dc,然后喚醒這個隊列翻斟。因為此時隊列為空,沒有任務(wù)在執(zhí)行说铃,處于休眠狀態(tài)访惜,所以需要喚醒
    _dispatch_retain(dq);
    dq->dq_items_head = obj;
    _dispatch_wakeup(dq);
    _dispatch_release(dq);
}

通過對異步任務(wù)入隊的分析嘹履,我們可以知道,入隊只是將任務(wù)一個一個以FIFO的順序添加到隊列中债热,那就是需要一個時間點去執(zhí)行這些任務(wù)砾嫉。

5.4 喚醒隊列:_dispatch_wakeup

無論是同步還是異步中都調(diào)用了_dispatch_wakeup這個函數(shù),這個函數(shù)的作用就是喚醒當(dāng)前隊列窒篱。

_dispatch_wakeup的源碼:

dispatch_queue_t _dispatch_wakeup(dispatch_object_t dou) {
    dispatch_queue_t tq;
    if (slowpath(DISPATCH_OBJECT_SUSPENDED(dou._do))) {
        return NULL;
    }

    if (!dx_probe(dou._do) && !dou._dq->dq_items_tail) {
        return NULL;
    }

    // 如果dou._do->do_suspend_cnt == 0焕刮,返回YES,否則返回NO;
    // 同時將DISPATCH_OBJECT_SUSPEND_LOCK賦值給dou._do->do_suspend_cnt
    if (!dispatch_atomic_cmpxchg2o(dou._do, do_suspend_cnt, 0, DISPATCH_OBJECT_SUSPEND_LOCK)) {
#if DISPATCH_COCOA_COMPAT
        // 主隊列的任務(wù)調(diào)用_dispatch_queue_wakeup_main喚醒主隊列
        if (dou._dq == &_dispatch_main_q) {
            _dispatch_queue_wakeup_main();
        }
#endif
        return NULL;
    }
    
    // 放到目標(biāo)隊列中墙杯,重新走_dispatch_queue_push方法
    _dispatch_retain(dou._do);
    tq = dou._do->do_targetq;
    _dispatch_queue_push(tq, dou._do);
    
    return tq;
}

上面的代碼中我們只看到了主隊列和其他自定義隊列的操作情況配并,但是沒有全局隊列的操作的情況,關(guān)于全局隊列的喚醒的比較隱晦高镐,針對全局隊列的dx_probe(dou._do)的調(diào)用如下:

#define dx_probe(x) (x)->do_vtable->do_probe(x)

// dx_probe(dou._do) 相當(dāng)于 (dou.do)->do_vtable->do_probe(dou.do)

// 全局隊列的do_vtable:_dispatch_queue_root_vtable

// _dispatch_queue_root_vtable的定義
static const struct dispatch_queue_vtable_s _dispatch_queue_root_vtable = {
    .do_type = DISPATCH_QUEUE_GLOBAL_TYPE,
    .do_kind = "global-queue",
    .do_debug = dispatch_queue_debug,
    .do_probe = _dispatch_queue_wakeup_global,
};

// 全局隊列:
// globalQueue -> _dispatch_queue_root_vtable -> _dispatch_queue_wakeup_global

從上面的代碼可以看出_dispatch_wakeup分為四種情況:

  1. 主隊列調(diào)用_dispatch_queue_wakeup_main溉旋;
  2. 全局隊列調(diào)用_dispatch_queue_wakeup_global
  3. 其他隊列向目標(biāo)隊列壓入這個隊列嫉髓,繼續(xù)做入隊操作观腊;
  4. 管理隊列調(diào)用_dispatch_mgr_wakeup,這里主要是為了dispatch_source而服務(wù)的算行。

_dispatch_queue_wakeup_main

_dispatch_main_queue_wakeup函數(shù)來喚醒主線程的Runloop梧油,之前在《重拾RunLoop原理》中提到:

使用GCD異步操作的時候,我們在一個子線程處理完一些事情后州邢,要返回主線程處理事情的時候婶溯,這時候需要依賴于RunLoop。

之前是控制臺打印驗證偷霉,現(xiàn)在我們在源碼中親自驗證:

void _dispatch_queue_wakeup_main(void) {
    kern_return_t kr;
    // 主要看_dispatch_main_q_port_init的實現(xiàn)
    dispatch_once_f(&_dispatch_main_q_port_pred, NULL,
            _dispatch_main_q_port_init);
    // 關(guān)于主線程的喚醒主要靠mach_port和在runloop中注冊相對應(yīng)的source1
    kr = _dispatch_send_wakeup_main_thread(main_q_port, 0);

    switch (kr) {
    case MACH_SEND_TIMEOUT:
    case MACH_SEND_TIMED_OUT:
    case MACH_SEND_INVALID_DEST:
        break;
    default:
        (void)dispatch_assume_zero(kr);
        break;
    }

    _dispatch_safe_fork = false;
}

// _dispatch_main_q_port_init的實現(xiàn)迄委,RunLoop的喚醒需要依賴于mach port
void _dispatch_main_q_port_init(void *ctxt DISPATCH_UNUSED) {
    kern_return_t kr;

    kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
            &main_q_port);
    DISPATCH_VERIFY_MIG(kr);
    (void)dispatch_assume_zero(kr);
    kr = mach_port_insert_right(mach_task_self(), main_q_port, main_q_port,
            MACH_MSG_TYPE_MAKE_SEND);
    DISPATCH_VERIFY_MIG(kr);
    (void)dispatch_assume_zero(kr);

    _dispatch_program_is_probably_callback_driven = true;
    _dispatch_safe_fork = false;
}

_dispatch_queue_wakeup_global

static bool
_dispatch_queue_wakeup_global(dispatch_queue_t dq) {
    static dispatch_once_t pred;
    struct dispatch_root_queue_context_s *qc = dq->do_ctxt;
    int r;

    if (!dq->dq_items_tail) return false;

    _dispatch_safe_fork = false;

    dispatch_debug_queue(dq, __PRETTY_FUNCTION__);
    
    // 上下文以及根隊列的初始化,根隊列內(nèi)部會初始化線程池
    dispatch_once_f(&pred, NULL, _dispatch_root_queues_init);
    
    // _dispatch_queue_wakeup_global支持兩種實現(xiàn)的任務(wù)喚醒pthread_workqueue和thread pool
    //  1.支持pthread_workqueue
#if HAVE_PTHREAD_WORKQUEUES
#if DISPATCH_ENABLE_THREAD_POOL
    // 如果隊列的dgq_kworkqueue存在类少,則調(diào)用pthread_workqueue_additem_np函數(shù)
    // dgq_kworkqueue是一個用于創(chuàng)建內(nèi)核線程的接口叙身,通過它創(chuàng)建的內(nèi)核線程來執(zhí)行內(nèi)核其他模塊排列到隊列里的工作。
    // 不同優(yōu)先級的dispatch queue對應(yīng)著對應(yīng)優(yōu)先級的workqueue硫狞。
    // _dispatch_root_queues_init初始化的時候信轿,使用pthread_workqueue_create_np創(chuàng)建pthread_workqueue
    if (qc->dgq_kworkqueue)
#endif
    {
        if (dispatch_atomic_cmpxchg2o(qc, dgq_pending, 0, 1)) {
            pthread_workitem_handle_t wh;
            unsigned int gen_cnt;
            _dispatch_debug("requesting new worker thread");
            // 該函數(shù)使用workq_kernreturn系統(tǒng)調(diào)用,通知workqueue增加應(yīng)當(dāng)執(zhí)行的項目残吩。
            // 根據(jù)該通知财忽,XNU內(nèi)核基于系統(tǒng)狀態(tài)判斷是否要生成線程,如果是overcommit優(yōu)先級的隊列泣侮,workqueue則始終生成線程即彪。
            r = pthread_workqueue_additem_np(qc->dgq_kworkqueue, _dispatch_worker_thread2, dq, &wh, &gen_cnt);
            (void)dispatch_assume_zero(r);
        } else {
            _dispatch_debug("work thread request still pending on global "
                    "queue: %p", dq);
        }
        goto out;
    }
#endif // HAVE_PTHREAD_WORKQUEUES
    // 2. 支持thread pool
#if DISPATCH_ENABLE_THREAD_POOL
    // 通過發(fā)送一個信號量使線程保活
    if (dispatch_semaphore_signal(qc->dgq_thread_mediator)) {
        goto out;
    }

    // 計算線程池可用長度活尊,如果線程池已滿則跳轉(zhuǎn)到out即return false隶校,否則執(zhí)行線程池-1操作
    pthread_t pthr;
    int t_count;
    do {
        t_count = qc->dgq_thread_pool_size;
        if (!t_count) {
            _dispatch_debug("The thread pool is full: %p", dq);
            goto out;
        }
    } while (!dispatch_atomic_cmpxchg2o(qc, dgq_thread_pool_size, t_count, t_count - 1));
    // qc->dgq_thread_pool_size的值與t_counts是否相等漏益,是就減1,并返回ture

    // 這里說明線程池不夠用了深胳,使用pthread創(chuàng)建一個線程绰疤,
    // 并執(zhí)行_dispatch_worker_thread,_dispatch_worker_thread最終會調(diào)用到_dispatch_worker_thread2
    while ((r = pthread_create(&pthr, NULL, _dispatch_worker_thread, dq))) {
        if (r != EAGAIN) {
            (void)dispatch_assume_zero(r);
        }
        sleep(1);
    }
    // 保證pthr能夠被回收
    r = pthread_detach(pthr);
    (void)dispatch_assume_zero(r);
#endif // DISPATCH_ENABLE_THREAD_POOL

out:
    return false;
}

5.5 隊列的任務(wù)調(diào)度

主隊列的任務(wù)調(diào)度:_dispatch_main_queue_callback_4CF

通過上面的源碼分析,我們知道主隊列在喚醒過程中會調(diào)用_dispatch_send_wakeup_main_thread函數(shù)舞终,但是該函數(shù)的實現(xiàn)并沒有開源這個函數(shù)的相關(guān)實現(xiàn)轻庆,似乎我們無法看出主隊列的任務(wù)調(diào)度。通過打印函數(shù)調(diào)用棧我們可以看到主隊列的任務(wù)調(diào)度是依賴_dispatch_main_queue_callback_4CF這個函數(shù)敛劝。

__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__之后調(diào)用_dispatch_main_queue_callback_4CF這個函數(shù)余爆。

// 處理主隊列任務(wù)
void _dispatch_main_queue_callback_4CF(mach_msg_header_t *msg DISPATCH_UNUSED) {
    if (main_q_is_draining) {
        return;
    }
    // 正在處理任務(wù),設(shè)置狀態(tài)為true
    _dispatch_queue_set_mainq_drain_state(true);
    // 調(diào)度處理任務(wù)
    _dispatch_main_queue_drain();
    // 處理任務(wù)完成任務(wù)攘蔽,恢復(fù)狀態(tài)為false
    _dispatch_queue_set_mainq_drain_state(false);
}

主隊列是一個串行隊列龙屉,按順序執(zhí)行,因此沒有并發(fā)的邏輯满俗。主隊列的任務(wù)調(diào)度就是順序遍歷转捕,主隊列喚起本次需要執(zhí)行的dc,并進行任務(wù)執(zhí)行唆垃,對于之后入隊的任務(wù)五芝,將放在下一輪的主隊列喚醒中執(zhí)行。這也是_dispatch_main_queue_drain函數(shù)的大致實現(xiàn)辕万。

全局隊列的任務(wù)調(diào)度:_dispatch_worker_thread2

全局隊列通過_dispatch_queue_wakeup_global函數(shù)枢步,將任務(wù)入隊。然后調(diào)用 _dispatch_worker_thread2函數(shù)處理對應(yīng)queue中的任務(wù)渐尿。

_dispatch_worker_thread2的實現(xiàn)中有兩個函數(shù)比較重要

  1. _dispatch_queue_concurrent_drain_one函數(shù)醉途;
  2. _dispatch_continuation_pop函數(shù);
_dispatch_queue_concurrent_drain_one

_dispatch_queue_concurrent_drain_one函數(shù)主要處理了以下幾件事情:

  • 多線程競爭下的邊界處理砖茸;
  • 獲取出隊dc
  • 再次喚醒全局隊列
static struct dispatch_object_s *
_dispatch_queue_concurrent_drain_one(dispatch_queue_t dq) {
    struct dispatch_object_s *head, *next, *const mediator = (void *)~0ul;

    // The mediator value acts both as a "lock" and a signal
    head = dispatch_atomic_xchg2o(dq, dq_items_head, mediator);

    // 1. 檢查隊列是否為空隘擎,是返回NULL
    if (slowpath(head == NULL)) {
        (void)dispatch_atomic_cmpxchg2o(dq, dq_items_head, mediator, NULL);
        _dispatch_debug("no work on global work queue");
        return NULL;
    }

    if (slowpath(head == mediator)) {
        // 該線程在現(xiàn)線程競爭中失去了對隊列的擁有權(quán),這意味著libdispatch的效率很糟糕凉夯,
        // 這種情況意味著在線程池中有太多的線程货葬,這個時候應(yīng)該創(chuàng)建一個pengding線程,
        // 然后退出該線程劲够,內(nèi)核會在負(fù)載減弱的時候創(chuàng)建一個新的線程
        _dispatch_debug("Contention on queue: %p", dq);
        _dispatch_queue_wakeup_global(dq);
#if DISPATCH_PERF_MON
        dispatch_atomic_inc(&_dispatch_bad_ratio);
#endif
        return NULL;
    }

    // 在返回之前將head指針的do_next保存下來震桶,如果next為NULL,這意味著item是最后一個
    next = fastpath(head->do_next);

    if (slowpath(!next)) {
        dq->dq_items_head = NULL;

        if (dispatch_atomic_cmpxchg2o(dq, dq_items_tail, head, NULL)) {
            // head和tail頭尾指針均為空
            goto out;
        }

        // 此時一定有item征绎,該線程不會等待太久蹲姐。
        while (!(next = head->do_next)) {
            _dispatch_hardware_pause();
        }
    }

    dq->dq_items_head = next;
    // 再次喚醒全局隊列
    _dispatch_queue_wakeup_global(dq);
out:
    // 返回需要處理的dc
    return head;
}

這里解釋下我對再次調(diào)用_dispatch_queue_wakeup_global喚醒全局隊列的理解:
我們知道并發(fā)隊列中的dc執(zhí)行是并發(fā)的,所以每一次出隊dc后檢查一下全局隊列,是否還有dc在隊列中淤堵。如果有就再次通知需要再創(chuàng)建一個work queue處理隊列中剩余的dc寝衫,然后重復(fù)上面的步驟顷扩,類似于一種遞歸的過程拐邪。當(dāng)多個work queue同時處理多個dc的時候,就是我們看到異步效果隘截。

_dispatch_continuation_pop

_dispatch_continuation_pop函數(shù)實現(xiàn)了對任務(wù)的處理扎阶。這些任務(wù)可能是異步任務(wù)、group任務(wù)婶芭、柵欄任務(wù)甚至可能就是隊列东臀。

static inline void
_dispatch_continuation_pop(dispatch_object_t dou) {
    dispatch_continuation_t dc = dou._dc;
    dispatch_group_t dg;

    _dispatch_trace_continuation_pop(_dispatch_queue_get_current(), dou);
    // 檢測是不是隊列,如果是犀农,就進入_dispatch_queue_invoke處理隊列
    // dispatch_barrier_async的任務(wù)會進入以下分支惰赋,以保證barrier任務(wù)和其他任務(wù)隔離,
    // 并通過dispath_semaphore_t實現(xiàn)通知barrier任務(wù)執(zhí)行
    if (DISPATCH_OBJ_IS_VTABLE(dou._do)) {
        return _dispatch_queue_invoke(dou._dq);
    }

    // Add the item back to the cache before calling the function. This
    // allows the 'hot' continuation to be used for a quick callback.
    //
    // The ccache version is per-thread.
    // Therefore, the object has not been reused yet.
    // This generates better assembly.
    // 是否是異步任務(wù)
    if ((long)dc->do_vtable & DISPATCH_OBJ_ASYNC_BIT) {
        _dispatch_continuation_free(dc);
    }
    
    // 判斷是否是group任務(wù)
    if ((long)dc->do_vtable & DISPATCH_OBJ_GROUP_BIT) {
        dg = dc->dc_group;
    } else {
        dg = NULL;
    }
    
    // 是任務(wù)封裝的dispatch_continuation_t結(jié)構(gòu)體(dc)呵哨,直接執(zhí)行任務(wù)赁濒。
    // 這也是異步的block被調(diào)用的時機
    _dispatch_client_callout(dc->dc_ctxt, dc->dc_func);
    if (dg) {
        // 如果是group執(zhí)行dispatch_group_leave
        dispatch_group_leave(dg);
        _dispatch_release(dg);
    }
}
_dispatch_queue_invoke
void
_dispatch_queue_invoke(dispatch_queue_t dq) {
    if (!slowpath(DISPATCH_OBJECT_SUSPENDED(dq)) &&
            fastpath(dispatch_atomic_cmpxchg2o(dq, dq_running, 0, 1))) {
        dispatch_atomic_acquire_barrier();
        dispatch_queue_t otq = dq->do_targetq, tq = NULL;
        _dispatch_queue_drain(dq);
        if (dq->do_vtable->do_invoke) {
            // Assume that object invoke checks it is executing on correct queue
            tq = dx_invoke(dq);
        } else if (slowpath(otq != dq->do_targetq)) {
            // An item on the queue changed the target queue
            tq = dq->do_targetq;
        }
        // We do not need to check the result.
        // When the suspend-count lock is dropped, then the check will happen.
        dispatch_atomic_release_barrier();
        //dq_running減1,因為任務(wù)要么被直接執(zhí)行了孟害,要么被壓到target隊列了
        (void)dispatch_atomic_dec2o(dq, dq_running);
        if (tq) {
            return _dispatch_queue_push(tq, dq);
        }
    }

    dq->do_next = DISPATCH_OBJECT_LISTLESS;
    if (!dispatch_atomic_sub2o(dq, do_suspend_cnt,
            DISPATCH_OBJECT_SUSPEND_LOCK)) {
        // 隊列處于空閑狀態(tài)拒炎,需要喚醒
        if (dq->dq_running == 0) {
            _dispatch_wakeup(dq); // verify that the queue is idle
        }
    }
    // 釋放隊列
    _dispatch_release(dq); // added when the queue is put on the list
}

現(xiàn)在我們整理一下隊列異步執(zhí)行的流程,如下圖:
dispatch_async

總結(jié)

  1. 隊列與線程可以是多對一關(guān)系挨务,一個線程上可以執(zhí)行不同隊列的任務(wù)击你,在主線程上一樣適用。

  2. 隊列操作與開啟線程的關(guān)系:

    • 串行隊列同步執(zhí)行任務(wù)谎柄,任務(wù)在當(dāng)前線程中有序執(zhí)行丁侄,如果前面的任務(wù)沒有完成則可能會阻塞當(dāng)前線程。
    • 串行隊列異步執(zhí)行任務(wù)朝巫,開啟一個線程鸿摇,任務(wù)在新線程中有序執(zhí)行,不會阻塞當(dāng)前線程捍歪,新線程中的任務(wù)有序執(zhí)行户辱。
    • 并發(fā)隊列同步執(zhí)行任務(wù),不會開啟新線程糙臼,任務(wù)在當(dāng)前線程中有序執(zhí)行庐镐,如果前面的任務(wù)沒有完成則可能會阻塞當(dāng)前線程。
    • 并發(fā)隊列異步執(zhí)行任務(wù)变逃,開啟多個線程必逆,具體數(shù)量由系統(tǒng)自己決定,任務(wù)在新開辟的線程中執(zhí)行,不會阻塞當(dāng)前線程名眉,所有任務(wù)為無序執(zhí)行粟矿。
    • 主隊列同步執(zhí)行任務(wù),死鎖损拢。
    • 主隊列異步執(zhí)行任務(wù)陌粹,任務(wù)在主線程中有序執(zhí)行,如果前面的任務(wù)沒有完成則可能會阻塞當(dāng)前線程福压。
  3. 隊列的同步/異步?jīng)Q定是否具備開啟線程的能力掏秩,隊列的串行/并發(fā)決定處理任務(wù)的個數(shù)。

  4. dispatch_queue通過結(jié)構(gòu)體和鏈表荆姆,被實現(xiàn)為FIFO(先進先出)隊列蒙幻,無論串行隊列和并發(fā)隊列,都是符合FIFO的原則胆筒。兩者的主要區(qū)別是:執(zhí)行順序不同邮破,以及開啟線程數(shù)不同。

  5. dispatch_sync函數(shù)一般都在當(dāng)前線程執(zhí)行仆救,利用與線程綁定的信號量來實現(xiàn)串行抒和。

  6. dispatch_async分發(fā)到主隊列的任務(wù)由Runloop處理,而分發(fā)到其他隊列的任務(wù)由線程池處理派桩。

  7. Block并不是直接添加到隊列上构诚,而是先構(gòu)成一個dispatch_continuation結(jié)構(gòu)體。結(jié)構(gòu)體包含了這個Block還有一些上下文信息铆惑。隊列會將這些dispatch_continuation結(jié)構(gòu)體添加隊列的鏈表中范嘱。無論這些隊列是什么類型的,最終都是和全局隊列相關(guān)的员魏。在全局隊列執(zhí)行Block的時候,libdispatch從全局隊列中取出dispatch_continuation丑蛤,調(diào)用pthread_workqueue_additem_np函數(shù),將該全局隊列自身撕阎、符合其優(yōu)先級的workqueue信息以及dispatch_continuation結(jié)構(gòu)體的回調(diào)函數(shù)傳遞給參數(shù)受裹。pthread_workqueue_additem_np函數(shù)使用workq_kernreturn系統(tǒng)調(diào)用,通知workqueue增加應(yīng)當(dāng)執(zhí)行的項目虏束。根據(jù)該同志棉饶,XNU內(nèi)核基于系統(tǒng)狀態(tài)判斷是否要生成線程。如果是overcommit優(yōu)先級的全局隊列workqueue則會始終生成線程镇匀。workqueue的線程執(zhí)行pthread_workqueue函數(shù)照藻,該函數(shù)調(diào)用libdispatch的回調(diào)函數(shù)。在該函數(shù)中執(zhí)行加入到dispatch_continuation的Block

  8. GCD死鎖是隊列導(dǎo)致的而不是線程導(dǎo)致汗侵,原因是_dispatch_barrier_sync_f_slow函數(shù)中使用了線程對應(yīng)的信號量并且調(diào)用wait方法幸缕,從而導(dǎo)致線程死鎖群发。

  9. 關(guān)于柵欄函數(shù)

    • dispatch_barrier_async適用的場景隊列必須是用DISPATCH_QUEUE_CONCURRENT屬性創(chuàng)建的隊列,而使用全局并發(fā)隊列的時候发乔,其表現(xiàn)就和dispatch_async一樣熟妓。原因:dispatch_barrier_async如果傳入的是全局隊列,在喚醒隊列時會執(zhí)行_dispatch_queue_wakeup_global函數(shù)栏尚,其執(zhí)行效果同dispatch_async一致起愈,而如果是自定義的隊列的時候,_dispatch_continuation_pop中會執(zhí)行dispatch_queue_invoke抵栈。在while循環(huán)中依次取出任務(wù)并調(diào)用_dispatch_continuation_redirect函數(shù)告材,使得block并發(fā)執(zhí)行坤次。當(dāng)遇到DISPATCH_OBJ_BARRIER_BIT標(biāo)記時古劲,會修改do_suspend_cnt標(biāo)志以保證后續(xù)while循環(huán)時直接goto out。barrier block的任務(wù)執(zhí)行完之后_dispatch_queue_class_invoke會將do_suspend_cnt重置回去缰猴,所以barrier block之后的任務(wù)會繼續(xù)執(zhí)行产艾。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市滑绒,隨后出現(xiàn)的幾起案子闷堡,更是在濱河造成了極大的恐慌,老刑警劉巖疑故,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杠览,死亡現(xiàn)場離奇詭異,居然都是意外死亡纵势,警方通過查閱死者的電腦和手機踱阿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钦铁,“玉大人软舌,你說我怎么就攤上這事∨2埽” “怎么了佛点?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長黎比。 經(jīng)常有香客問我虎谢,道長,這世上最難降的妖魔是什么缘琅? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任荠藤,我火速辦了婚禮,結(jié)果婚禮上书妻,老公的妹妹穿的比我還像新娘船响。我一直安慰自己躬拢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布见间。 她就那樣靜靜地躺著聊闯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪米诉。 梳的紋絲不亂的頭發(fā)上菱蔬,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音史侣,去河邊找鬼拴泌。 笑死,一個胖子當(dāng)著我的面吹牛惊橱,可吹牛的內(nèi)容都是我干的蚪腐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼税朴,長吁一口氣:“原來是場噩夢啊……” “哼回季!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起正林,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤泡一,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后觅廓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鼻忠,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年杈绸,在試婚紗的時候發(fā)現(xiàn)自己被綠了帖蔓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡蝇棉,死狀恐怖讨阻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情篡殷,我是刑警寧澤钝吮,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站板辽,受9級特大地震影響奇瘦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜劲弦,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一耳标、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧邑跪,春花似錦次坡、人聲如沸呼猪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宋距。三九已至,卻和暖如春症脂,著一層夾襖步出監(jiān)牢的瞬間谚赎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工诱篷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留壶唤,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓棕所,卻偏偏與公主長得像闸盔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子橙凳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容