iOS GCD 之 底層原理分析

本文是隊(duì)列創(chuàng)建隘擎、同步/異步函數(shù)边苹、單例信號量以及調(diào)度組的底層原理分析

隊(duì)列創(chuàng)建

在上一篇文章GCD 之 函數(shù)與隊(duì)列中杜顺,我們理解了隊(duì)列與函數(shù)财搁,知道隊(duì)列的創(chuàng)建時(shí)通過GCD中的dispatch_queue_create方法,下面我們在libdispatch.dylib去探索隊(duì)列是如何創(chuàng)建的(下載鏈接

底層源碼分析

  • 在源碼中搜索dispatch_queue_create
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(
DISPATCH_NOINLINE
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);

    //第一步:規(guī)范化參數(shù),例如qos, overcommit, tq
    ...

    //拼接隊(duì)列名稱
    const void *vtable;
    dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
    if (dqai.dqai_concurrent) { //vtable表示類的類型
        // OS_dispatch_queue_concurrent
        vtable = DISPATCH_VTABLE(queue_concurrent);
    } else {
        vtable = DISPATCH_VTABLE(queue_serial);
    }

    ....

    //創(chuàng)建隊(duì)列,并初始化
    dispatch_lane_t dq = _dispatch_object_alloc(vtable,
            sizeof(struct dispatch_lane_s)); // alloc
    //根據(jù)dqai.dqai_concurrent的值征绎,就能判斷隊(duì)列 是 串行 還是并發(fā)
    _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
    //設(shè)置隊(duì)列l(wèi)abel標(biāo)識符
    dq->dq_label = label;//label賦值
    dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos, dqai.dqai_relpri);//優(yōu)先級處理

    ...

    //類似于類與元類的綁定获洲,不是直接的繼承關(guān)系提茁,而是類似于模型與模板的關(guān)系
    dq->do_targetq = tq;
    _dispatch_object_debug(dq, "%s", __func__);
    return _dispatch_trace_queue_create(dq)._dq;//研究dq
}

_dispatch_lane_create_with_target 分析

  • 【第一步】通過_dispatch_queue_attr_to_info方法傳入dqa(即隊(duì)列類型淹禾,串行、并發(fā)等)創(chuàng)建dispatch_queue_attr_info_t類型的對象dqai茴扁,用于存儲(chǔ)隊(duì)列的相關(guān)屬性信息

  • 【第二步】設(shè)置隊(duì)列相關(guān)聯(lián)的屬性铃岔,例如服務(wù)質(zhì)量qos等

    • 【第三步】通過DISPATCH_VTABLE拼接隊(duì)列名稱,即vtable丹弱,其中DISPATCH_VTABLE是宏定義德撬,如下所示铲咨,所以隊(duì)列的類型是通過OS_dispatch_+隊(duì)列類型queue_concurrent拼接而成的

    • 串行隊(duì)列類型:OS_dispatch_queue_serial躲胳,驗(yàn)證如下

并發(fā)隊(duì)列類型:OS_dispatch_queue_concurrent,驗(yàn)證如下

#define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)
??
#define DISPATCH_OBJC_CLASS(name)   (&DISPATCH_CLASS_SYMBOL(name))
??
#define DISPATCH_CLASS(name) OS_dispatch_##name

第四步】通過alloc+init初始化隊(duì)列纤勒,即dq坯苹,其中在_dispatch_queue_init傳參中根據(jù)dqai.dqai_concurrent的布爾值,就能判斷隊(duì)列 是 串行 還是并發(fā)摇天,而 vtable表示隊(duì)列的類型粹湃,說明隊(duì)列也是對象

  • 進(jìn)入_dispatch_object_alloc -> _os_object_alloc_realized方法中設(shè)置了isa的指向,從這里可以驗(yàn)證隊(duì)列也是對象的說法

進(jìn)入_dispatch_queue_init方法,隊(duì)列類型是dispatch_queue_t,并設(shè)置隊(duì)列的相關(guān)屬性

【第五步】通過_dispatch_trace_queue_create對創(chuàng)建的隊(duì)列進(jìn)行處理泉坐,其中_dispatch_trace_queue_create_dispatch_introspection_queue_create封裝的宏定義为鳄,最后會(huì)返回處理過的_dq

進(jìn)入_dispatch_introspection_queue_create_hook -> dispatch_introspection_queue_get_info -> _dispatch_introspection_lane_get_info中可以看出,與我們自定義的類還是有所區(qū)別的腕让,創(chuàng)建隊(duì)列在底層的實(shí)現(xiàn)是通過模板創(chuàng)建

總結(jié)

  • 隊(duì)列創(chuàng)建方法dispatch_queue_create中的參數(shù)二(即隊(duì)列類型)孤钦,決定了下層中 max & 1(用于區(qū)分是 串行 還是 并發(fā)),其中1表示串行

  • queue 也是一個(gè)對象纯丸,也需要底層通過alloc + init 創(chuàng)建偏形,并且在alloc中也有一個(gè)class,這個(gè)class是通過宏定義拼接而成觉鼻,并且同時(shí)會(huì)指定isa的指向

  • 創(chuàng)建隊(duì)列在底層的處理是通過模板創(chuàng)建的俊扭,其類型是dispatch_introspection_queue_s結(jié)構(gòu)體

dispatch_queue_create底層分析流程如下圖所示

函數(shù) 底層原理分析

主要是分析 異步函數(shù)dispatch_async 和 同步函數(shù)dispatch_sync

異步函數(shù)

進(jìn)入dispatch_async的源碼實(shí)現(xiàn),主要分析兩個(gè)函數(shù)

  • _dispatch_continuation_init:任務(wù)包裝函數(shù)

  • _dispatch_continuation_async:并發(fā)處理函數(shù)

void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)//work 任務(wù)
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME;
    dispatch_qos_t qos;

    // 任務(wù)包裝器(work在這里才有使用) - 接受work - 保存work - 并函數(shù)式編程
    // 保存 block 
    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    //并發(fā)處理
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}

_dispatch_continuation_init 任務(wù)包裝器

進(jìn)入_dispatch_continuation_init源碼實(shí)現(xiàn)坠陈,主要是包裝任務(wù)萨惑,并設(shè)置線程的回程函數(shù),相當(dāng)于初始化

DISPATCH_ALWAYS_INLINE
static inline dispatch_qos_t
_dispatch_continuation_init(dispatch_continuation_t dc,
        dispatch_queue_class_t dqu, dispatch_block_t work,
        dispatch_block_flags_t flags, uintptr_t dc_flags)
{
    void *ctxt = _dispatch_Block_copy(work);//拷貝任務(wù)

    dc_flags |= DC_FLAG_BLOCK | DC_FLAG_ALLOCATED;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        dc->dc_flags = dc_flags;
        dc->dc_ctxt = ctxt;//賦值
        // will initialize all fields but requires dc_flags & dc_ctxt to be set
        return _dispatch_continuation_init_slow(dc, dqu, flags);
    }

    dispatch_function_t func = _dispatch_Block_invoke(work);//封裝work - 異步回調(diào)
    if (dc_flags & DC_FLAG_CONSUME) {
        func = _dispatch_call_block_and_release;//回調(diào)函數(shù)賦值 - 同步回調(diào)
    }
    return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}

主要有以下幾步

  • 通過_dispatch_Block_copy拷貝任務(wù)

  • 通過_dispatch_Block_invoke封裝任務(wù)仇矾,其中_dispatch_Block_invoke是個(gè)宏定義庸蔼,根據(jù)以上分析得知是異步回調(diào)

#define _dispatch_Block_invoke(bb) \
        ((dispatch_function_t)((struct Block_layout *)bb)->invoke)

  • 如果是同步的,則回調(diào)函數(shù)賦值為_dispatch_call_block_and_release

  • 通過_dispatch_continuation_init_f方法將回調(diào)函數(shù)賦值若未,即f就是func朱嘴,將其保存在屬性中

_dispatch_continuation_async 并發(fā)處理

這個(gè)函數(shù)中,主要是執(zhí)行block回調(diào)

  • 進(jìn)入_dispatch_continuation_async的源碼實(shí)現(xiàn)
DISPATCH_ALWAYS_INLINE
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_invoke一樣,都是宏
}

  • 其中的關(guān)鍵代碼是dx_push(dqu._dq, dc, qos)萍嬉,dx_push是宏定義乌昔,如下所示
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)

  • 而其中的dq_push需要根據(jù)隊(duì)列的類型,執(zhí)行不同的函數(shù)

符號斷點(diǎn)調(diào)試執(zhí)行函數(shù)

  • 運(yùn)行demo壤追,通過符號斷點(diǎn)磕道,來判斷執(zhí)行的是哪個(gè)函數(shù),由于是并發(fā)隊(duì)列行冰,通過增加_dispatch_lane_concurrent_push符號斷點(diǎn)溺蕉,看看是否會(huì)走到這里
dispatch_queue_t conque = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conque, ^{
    NSLog(@"異步函數(shù)");
});

  • 運(yùn)行發(fā)現(xiàn),走的確實(shí)是_dispatch_lane_concurrent_push

進(jìn)入_dispatch_lane_concurrent_push源碼悼做,發(fā)現(xiàn)有兩步疯特,繼續(xù)通過符號斷點(diǎn)_dispatch_continuation_redirect_push_dispatch_lane_push調(diào)試,發(fā)現(xiàn)走的是_dispatch_continuation_redirect_push

進(jìn)入_dispatch_continuation_redirect_push源碼肛走,發(fā)現(xiàn)又走到了dx_push漓雅,即遞歸了,綜合前面隊(duì)列創(chuàng)建時(shí)可知朽色,隊(duì)列也是一個(gè)對象邻吞,有父類、根類葫男,所以會(huì)遞歸執(zhí)行到根類的方法

接下來抱冷,通過根類的_dispatch_root_queue_push符號斷點(diǎn),來驗(yàn)證猜想是否正確梢褐,從運(yùn)行結(jié)果看出旺遮,完全是正確的

  • 進(jìn)入_dispatch_root_queue_push -> _dispatch_root_queue_push_inline ->_dispatch_root_queue_poke -> _dispatch_root_queue_poke_slow源碼,經(jīng)過符號斷點(diǎn)驗(yàn)證利职,確實(shí)是走的這里趣效,查看該方法的源碼實(shí)現(xiàn),主要有兩步操作

    • 通過_dispatch_root_queues_init方法注冊回調(diào)

    • 通過do-while循環(huán)創(chuàng)建線程猪贪,使用pthread_create方法

DISPATCH_NOINLINE
static void
_dispatch_root_queue_poke_slow(dispatch_queue_global_t dq, int n, int floor)
{
    int remaining = n;
    int r = ENOSYS;

    _dispatch_root_queues_init();//重點(diǎn)

    ...
    //do-while循環(huán)創(chuàng)建線程
    do {
        _dispatch_retain(dq); // released in _dispatch_worker_thread
        while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) {
            if (r != EAGAIN) {
                (void)dispatch_assume_zero(r);
            }
            _dispatch_temporary_resource_shortage();
        }
    } while (--remaining);

    ...
}

_dispatch_root_queues_init
  • 進(jìn)入_dispatch_root_queues_init源碼實(shí)現(xiàn)跷敬,發(fā)現(xiàn)是一個(gè)dispatch_once_f單例(請查看后續(xù)單例的底層分析們,這里不作說明)热押,其中傳入的func_dispatch_root_queues_init_once西傀。
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_root_queues_init(void)
{
    dispatch_once_f(&_dispatch_root_queues_pred, NULL, _dispatch_root_queues_init_once);
}

  • 進(jìn)入_dispatch_root_queues_init_once的源碼,其內(nèi)部不同事務(wù)的調(diào)用句柄都是_dispatch_worker_thread2

    image

其block回調(diào)執(zhí)行的調(diào)用路徑為:_dispatch_root_queues_init_once ->_dispatch_worker_thread2 -> _dispatch_root_queue_drain -> _dispatch_root_queue_drain -> _dispatch_continuation_pop_inline -> _dispatch_continuation_invoke_inline -> _dispatch_client_callout -> dispatch_call_block_and_release

這個(gè)路徑可以通過斷點(diǎn)桶癣,bt打印堆棧信息得出

說明

在這里需要說明一點(diǎn)的是拥褂,單例block回調(diào)和異步函數(shù)block回調(diào)是不同的

  • 單例中,block回調(diào)中的func_dispatch_Block_invoke(block)

  • 異步函數(shù)中牙寞,block回調(diào)中的funcdispatch_call_block_and_release

總結(jié)

所以饺鹃,綜上所述莫秆,異步函數(shù)的底層分析如下

  • 【準(zhǔn)備工作】:首先,將異步任務(wù)拷貝并封裝悔详,并設(shè)置回調(diào)函數(shù)func

  • 【block回調(diào)】:底層通過dx_push遞歸镊屎,會(huì)重定向到根隊(duì)列,然后通過pthread_creat創(chuàng)建線程茄螃,最后通過dx_invoke執(zhí)行block回調(diào)(注意dx_pushdx_invoke 是成對的)

異步函數(shù)的底層分析流程如圖所示

dispatch_async底層分析流程

同步函數(shù)

進(jìn)入dispatch_sync源碼實(shí)現(xiàn)缝驳,其底層的實(shí)現(xiàn)是通過柵欄函數(shù)實(shí)現(xiàn)的(柵欄函數(shù)的底層分析見后文)

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

  • 進(jìn)入_dispatch_sync_f源碼

  • 查看_dispatch_sync_f_inline源碼,其中width = 1表示是串行隊(duì)列归苍,其中有兩個(gè)重點(diǎn):

    • 柵欄:_dispatch_barrier_sync_f(可以通過后文的柵欄函數(shù)底層分析解釋)用狱,可以得出同步函數(shù)底層實(shí)現(xiàn)其實(shí)是同步柵欄函數(shù)

    • 死鎖:_dispatch_sync_f_slow,如果存在相互等待的情況拼弃,就會(huì)造成死鎖

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
        dispatch_function_t func, uintptr_t dc_flags)
{
    if (likely(dq->dq_width == 1)) {//表示是串行隊(duì)列
        return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);//柵欄
    }

    if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
        DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
    }

    dispatch_lane_t dl = upcast(dq)._dl;
    // Global concurrent queues and queues bound to non-dispatch threads
    // always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
    if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {
        return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);//死鎖
    }

    if (unlikely(dq->do_targetq->do_targetq)) {
        return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);
    }
    _dispatch_introspection_sync_begin(dl);//處理當(dāng)前信息
    _dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
            _dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));//block執(zhí)行并釋放
}

_dispatch_sync_f_slow 死鎖

  • 進(jìn)入_dispatch_sync_f_slow夏伊,當(dāng)前的主隊(duì)列掛起、阻塞

往一個(gè)隊(duì)列中 加入任務(wù)肴敛,會(huì)push加入主隊(duì)列署海,進(jìn)入_dispatch_trace_item_push

進(jìn)入__DISPATCH_WAIT_FOR_QUEUE__,判斷dq是否為正在等待的隊(duì)列医男,然后給出一個(gè)狀態(tài)state,然后將dq的狀態(tài)和當(dāng)前任務(wù)依賴的隊(duì)列進(jìn)行匹配

  • 進(jìn)入_dq_state_drain_locked_by -> _dispatch_lock_is_locked_by源碼

DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
    // equivalent to _dispatch_lock_owner(lock_value) == tid
    //異或操作:相同為0捻勉,不同為1镀梭,如果相同,則為0踱启,0 &任何數(shù)都為0
    //即判斷 當(dāng)前要等待的任務(wù) 和 正在執(zhí)行的任務(wù)是否一樣报账,通俗的解釋就是 執(zhí)行和等待的是否在同一隊(duì)列
    return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}

如果當(dāng)前等待的和正在執(zhí)行的是同一個(gè)隊(duì)列,即判斷線程ID是否相乘埠偿,如果相等透罢,則會(huì)造成死鎖

同步函數(shù) + 并發(fā)隊(duì)列 順序執(zhí)行的原因

_dispatch_sync_invoke_and_complete -> _dispatch_sync_function_invoke_inline源碼中,主要有三個(gè)步驟:

  • 將任務(wù)壓入隊(duì)列: _dispatch_thread_frame_push
  • 執(zhí)行任務(wù)的block回調(diào): _dispatch_client_callout
  • 將任務(wù)出隊(duì):_dispatch_thread_frame_pop

從實(shí)現(xiàn)中可以看出冠蒋,是先將任務(wù)push隊(duì)列中羽圃,然后執(zhí)行block回調(diào),在將任務(wù)pop抖剿,所以任務(wù)是順序執(zhí)行的朽寞。

總結(jié)

同步函數(shù)的底層實(shí)現(xiàn)如下:

  • 同步函數(shù)的底層實(shí)現(xiàn)實(shí)際是同步柵欄函數(shù)

  • 同步函數(shù)中如果當(dāng)前正在執(zhí)行的隊(duì)列和等待的是同一個(gè)隊(duì)列,形成相互等待的局面斩郎,則會(huì)造成死鎖

所以脑融,綜上所述,同步函數(shù)的底層實(shí)現(xiàn)流程如圖所示

單例

在日常開發(fā)中缩宜,我們一般使用GCD的dispatch_once來創(chuàng)建單例肘迎,如下所示

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    NSLog(@"單例應(yīng)用");
});

首先對于單例,我們需要了解兩點(diǎn)

  • 【執(zhí)行一次的原因】單例的流程只執(zhí)行一次,底層是如何控制的妓布,即為什么只能執(zhí)行一次窿侈?
  • 【block調(diào)用時(shí)機(jī)】單例的block是在什么時(shí)候進(jìn)行調(diào)用的?

下面帶著以下兩點(diǎn)疑問秋茫,我們來針對單例的底層進(jìn)行分析

  • 進(jìn)入dispatch_once源碼實(shí)現(xiàn),底層是通過dispatch_once_f實(shí)現(xiàn)的
    • 參數(shù)1:onceToken史简,它是一個(gè)靜態(tài)變量,由于不同位置定義的靜態(tài)變量是不同的肛著,所以靜態(tài)變量具有唯一性

    • 參數(shù)2:block回調(diào)

void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}

  • 進(jìn)入dispatch_once_f源碼圆兵,其中的val 是外界傳入的onceToken靜態(tài)變量,而func_dispatch_Block_invoke(block)枢贿,其中單例的底層主要分為以下幾步
    • val殉农,也就是靜態(tài)變量轉(zhuǎn)換為dispatch_once_gate_t類型的變量l

    • 通過os_atomic_load獲取此時(shí)的任務(wù)的標(biāo)識符v

      • 如果v等于DLOCK_ONCE_DONE,表示任務(wù)已經(jīng)執(zhí)行過了局荚,直接return

      • 如果 任務(wù)執(zhí)行后超凳,加鎖失敗了,則走到_dispatch_once_mark_done_if_quiesced函數(shù)耀态,再次進(jìn)行存儲(chǔ)轮傍,將標(biāo)識符置為DLOCK_ONCE_DONE

      • 反之,則通過_dispatch_once_gate_tryenter嘗試進(jìn)入任務(wù)首装,即解鎖创夜,然后執(zhí)行_dispatch_once_callout執(zhí)行block回調(diào)

    • 如果此時(shí)有任務(wù)正在執(zhí)行,再次進(jìn)來一個(gè)任務(wù)2仙逻,則通過_dispatch_once_wait函數(shù)讓任務(wù)2進(jìn)入無限次等待

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);//load
    if (likely(v == DLOCK_ONCE_DONE)) {//已經(jīng)執(zhí)行過了驰吓,直接返回
        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)) {//嘗試進(jìn)入
        return _dispatch_once_callout(l, ctxt, func);
    }
    return _dispatch_once_wait(l);//無限次等待
}

_dispatch_once_gate_tryenter 解鎖

查看其源碼,主要是通過底層os_atomic_cmpxchg方法進(jìn)行對比系奉,如果比較沒有問題檬贰,則進(jìn)行加鎖,即任務(wù)的標(biāo)識符置為DLOCK_ONCE_UNLOCKED

DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
    return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
            (uintptr_t)_dispatch_lock_value_for_self(), relaxed);//首先對比缺亮,然后進(jìn)行改變
}

_dispatch_once_callout 回調(diào)

進(jìn)入_dispatch_once_callout源碼翁涤,主要就兩步

  • _dispatch_client_callout:block回調(diào)執(zhí)行

  • _dispatch_once_gate_broadcast:進(jìn)行廣播

DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
        dispatch_function_t func)
{
    _dispatch_client_callout(ctxt, func);//block調(diào)用執(zhí)行
    _dispatch_once_gate_broadcast(l);//進(jìn)行廣播:告訴別人有了歸屬,不要找我了

  • 進(jìn)入_dispatch_client_callout源碼瞬内,主要就是執(zhí)行block回調(diào)迷雪,其中的f等于_dispatch_Block_invoke(block),即異步回調(diào)
#undef _dispatch_client_callout
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
    @try {
        return f(ctxt);
    }
    @catch (...) {
        objc_terminate();
    }
}

  • 進(jìn)入 _dispatch_once_gate_broadcast -> _dispatch_once_mark_done源碼虫蝶,主要就是給dgo->dgo_once一個(gè)值章咧,然后將任務(wù)的標(biāo)識符為DLOCK_ONCE_DONE,即解鎖
DISPATCH_ALWAYS_INLINE
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
    //如果不相同能真,直接改為相同赁严,然后上鎖 -- DLOCK_ONCE_DONE
    return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}

總結(jié)

針對單例的底層實(shí)現(xiàn)扰柠,主要說明如下:

  • 【單例只執(zhí)行一次的原理】:GCD單例中,有兩個(gè)重要參數(shù)疼约,onceTokenblock卤档,其中onceToken是靜態(tài)變量,具有唯一性程剥,在底層被封裝成了dispatch_once_gate_t類型的變量l劝枣,l主要是用來獲取底層原子封裝性的關(guān)聯(lián),即變量v织鲸,通過v來查詢?nèi)蝿?wù)的狀態(tài)舔腾,如果此時(shí)v等于DLOCK_ONCE_DONE,說明任務(wù)已經(jīng)處理過一次了搂擦,直接return

  • 【block調(diào)用時(shí)機(jī)】:如果此時(shí)任務(wù)沒有執(zhí)行過稳诚,則會(huì)在底層通過C++函數(shù)的比較,將任務(wù)進(jìn)行加鎖瀑踢,即任務(wù)狀態(tài)置為DLOCK_ONCE_UNLOCK扳还,目的是為了保證當(dāng)前任務(wù)執(zhí)行的唯一性,防止在其他地方有多次定義橱夭。加鎖之后進(jìn)行block回調(diào)函數(shù)的執(zhí)行氨距,執(zhí)行完成后,將當(dāng)前任務(wù)解鎖徘钥,將當(dāng)前的任務(wù)狀態(tài)置為DLOCK_ONCE_DONE衔蹲,在下次進(jìn)來時(shí),就不會(huì)在執(zhí)行呈础,會(huì)直接返回

  • 【多線程影響】:如果在當(dāng)前任務(wù)執(zhí)行期間,有其他任務(wù)進(jìn)來橱健,會(huì)進(jìn)入無限次等待而钞,原因是當(dāng)前任務(wù)已經(jīng)獲取了鎖,進(jìn)行了加鎖拘荡,其他任務(wù)是無法獲取鎖的

單例的底層流程分析如下如所示

柵欄函數(shù)

GCD中常用的柵欄函數(shù)臼节,主要有兩種

  • 同步柵欄函數(shù)dispatch_barrier_sync(在主線程中執(zhí)行):前面的任務(wù)執(zhí)行完畢才會(huì)來到這里,但是同步柵欄函數(shù)會(huì)堵塞線程珊皿,影響后面的任務(wù)執(zhí)行

  • 異步柵欄函數(shù)dispatch_barrier_async:前面的任務(wù)執(zhí)行完畢才會(huì)來到這里

柵欄函數(shù)最直接的作用就是 控制任務(wù)執(zhí)行順序网缝,使同步執(zhí)行

同時(shí)蟋定,柵欄函數(shù)需要注意一下幾點(diǎn)

  • 柵欄函數(shù)控制同一并發(fā)隊(duì)列
  • 同步柵欄添加進(jìn)入隊(duì)列的時(shí)候粉臊,當(dāng)前線程會(huì)被鎖死,直到同步柵欄之前的任務(wù)和同步柵欄任務(wù)本身執(zhí)行完畢時(shí)驶兜,當(dāng)前線程才會(huì)打開然后繼續(xù)執(zhí)行下一句代碼扼仲。
  • 在使用柵欄函數(shù)時(shí).使用自定義隊(duì)列才有意義,如果用的是串行隊(duì)列,這個(gè)柵欄函數(shù)的作用等同于一個(gè)同步函數(shù)的作用远寸,沒有任何意義;如果系統(tǒng)提供的全局并發(fā)隊(duì)列,由于全局并發(fā)隊(duì)列不是只有你在使用屠凶,系統(tǒng)也有可能在使用驰后,可能引起不必要的問題。

代碼調(diào)試

總共有4個(gè)任務(wù)矗愧,其中前2個(gè)任務(wù)有依賴關(guān)系灶芝,即任務(wù)1執(zhí)行完,執(zhí)行任務(wù)2唉韭,此時(shí)可以使用柵欄函數(shù)

  • 異步柵欄函數(shù) 不會(huì)阻塞主線程 夜涕,異步 堵塞 的是隊(duì)列

  • 同步柵欄函數(shù) 會(huì)堵塞主線程,同步 堵塞 是當(dāng)前的線程

總結(jié)

  • 異步柵欄函數(shù)阻塞的是隊(duì)列纽哥,而且必須是自定義的并發(fā)隊(duì)列钠乏,不影響主線程任務(wù)的執(zhí)行

  • 同步柵欄函數(shù)阻塞的是線程,且是主線程春塌,會(huì)影響主線程其他任務(wù)的執(zhí)行

使用場景

柵欄函數(shù)除了用于任務(wù)有依賴關(guān)系時(shí)晓避,同時(shí)還可以用于數(shù)據(jù)安全

像下面這樣操作,會(huì)崩潰

崩潰的原因是:數(shù)據(jù)在不斷的retain 和 release只壳,在數(shù)據(jù)還沒有retain完畢時(shí)俏拱,已經(jīng)開始了release,相當(dāng)于加了一個(gè)空數(shù)據(jù)吼句,進(jìn)行release

修改

  • 通過加?xùn)艡诤瘮?shù)
- (void)use041{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);

    NSMutableArray *array = [NSMutableArray array];

    for (int i = 0; i<100000; i++) {
        dispatch_async(concurrentQueue, ^{
            dispatch_barrier_async(concurrentQueue, ^{
                [array addObject:[NSString stringWithFormat:@"%d", i]];
            });
        });
    }
}

  • 使用互斥鎖@synchronized (self) {}
- (void)use041{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);

    NSMutableArray *array = [NSMutableArray array];

    for (int i = 0; i<100000; i++) {
        dispatch_async(concurrentQueue, ^{
            @synchronized (self) {
                [array addObject:[NSString stringWithFormat:@"%d", i]];
            };
        });
    }
}

注意

  • 如果柵欄函數(shù)中使用 全局隊(duì)列锅必, 運(yùn)行會(huì)崩潰,原因是系統(tǒng)也在用全局并發(fā)隊(duì)列惕艳,使用柵欄同時(shí)會(huì)攔截系統(tǒng)的搞隐,所以會(huì)崩潰

  • 如果將自定義并發(fā)隊(duì)列改為串行隊(duì)列,即serial 远搪,串行隊(duì)列本身就是有序同步 此時(shí)加?xùn)艡诹痈伲瑫?huì)浪費(fèi)性能

  • 柵欄函數(shù)只會(huì)阻塞一次

異步柵欄函數(shù) 底層分析

進(jìn)入dispatch_barrier_async源碼實(shí)現(xiàn),其底層的實(shí)現(xiàn)與dispatch_async類似谁鳍,這里就不再做分析了癞季,有興趣的可以自行探索下

#ifdef __BLOCKS__
void
dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work)
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
    dispatch_qos_t qos;

    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc_flags);
}
#endif

同步柵欄函數(shù) 底層分析

進(jìn)入dispatch_barrier_sync源碼,實(shí)現(xiàn)如下

void
dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work)
{
    uintptr_t dc_flags = DC_FLAG_BARRIER | DC_FLAG_BLOCK;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    }
    _dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

_dispatch_barrier_sync_f_inline

進(jìn)入_dispatch_barrier_sync_f -> _dispatch_barrier_sync_f_inline源碼

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt,
        dispatch_function_t func, uintptr_t dc_flags)
{
    dispatch_tid tid = _dispatch_tid_self();//獲取線程的id倘潜,即線程的唯一標(biāo)識

    ...

    //判斷線程狀態(tài)绷柒,需不需要等待,是否回收
    if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {//柵欄函數(shù)也會(huì)死鎖
        return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,//沒有回收
                DC_FLAG_BARRIER | dc_flags);
    }
    //驗(yàn)證target是否存在涮因,如果存在废睦,加入柵欄函數(shù)的遞歸查找 是否等待
    if (unlikely(dl->do_targetq->do_targetq)) {
        return _dispatch_sync_recurse(dl, ctxt, func,
                DC_FLAG_BARRIER | dc_flags);
    }
    _dispatch_introspection_sync_begin(dl);
    _dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
            DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
                    dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));//執(zhí)行
}

源碼主要有分為以下幾部分

  • 通過_dispatch_tid_self獲取線程ID

  • 通過_dispatch_queue_try_acquire_barrier_sync判斷線程狀態(tài)

進(jìn)入_dispatch_queue_try_acquire_barrier_sync_and_suspend,在這里進(jìn)行釋放

  • 通過_dispatch_sync_recurse遞歸查找柵欄函數(shù)的target

  • 通過_dispatch_introspection_sync_begin對向前信息進(jìn)行處理

通過_dispatch_lane_barrier_sync_invoke_and_complete執(zhí)行block并釋放

信號量

信號量的作用一般是用來使任務(wù)同步執(zhí)行蕊退,類似于互斥鎖郊楣,用戶可以根據(jù)需要控制GCD最大并發(fā)數(shù)憔恳,一般是這樣使用的

//信號量
dispatch_semaphore_t sem = dispatch_semaphore_create(1);

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(sem);

下面我們來分析其底層原理

dispatch_semaphore_create 創(chuàng)建

該函數(shù)的底層實(shí)現(xiàn)如下,主要是初始化信號量净蚤,并設(shè)置GCD的最大并發(fā)數(shù)钥组,其最大并發(fā)數(shù)必須大于0

dispatch_semaphore_t
dispatch_semaphore_create(long value)
{
    dispatch_semaphore_t dsema;

    // If the internal value is negative, then the absolute of the value is
    // equal to the number of waiting threads. Therefore it is bogus to
    // initialize the semaphore with a negative value.
    if (value < 0) {
        return DISPATCH_BAD_INPUT;
    }

    dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
            sizeof(struct dispatch_semaphore_s));
    dsema->do_next = DISPATCH_OBJECT_LISTLESS;
    dsema->do_targetq = _dispatch_get_default_queue(false);
    dsema->dsema_value = value;
    _dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
    dsema->dsema_orig = value;
    return dsema;
}

dispatch_semaphore_wait 加鎖

該函數(shù)的源碼實(shí)現(xiàn)如下,其主要作用是對信號量dsema通過os_atomic_dec2o進(jìn)行了--操作今瀑,其內(nèi)部是執(zhí)行的C++的atomic_fetch_sub_explicit方法

  • 如果value 大于等于0程梦,表示操作無效,即執(zhí)行成功

  • 如果value 等于LONG_MIN橘荠,系統(tǒng)會(huì)拋出一個(gè)crash

  • 如果value 小于0屿附,則進(jìn)入長等待

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
    // dsema_value 進(jìn)行 -- 操作
    long value = os_atomic_dec2o(dsema, dsema_value, acquire);
    if (likely(value >= 0)) {//表示執(zhí)行操作無效,即執(zhí)行成功
        return 0;
    }
    return _dispatch_semaphore_wait_slow(dsema, timeout);//長等待
}

其中os_atomic_dec2o的宏定義轉(zhuǎn)換如下

os_atomic_inc2o(p, f, m) 
??
os_atomic_sub2o(p, f, 1, m)
??
_os_atomic_c11_op((p), (v), m, sub, -)
??
_os_atomic_c11_op((p), (v), m, add, +)
??
({ _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_dec2o(dsema, dsema_value, acquire);

os_atomic_sub2o(dsema, dsema_value, 1, m)

os_atomic_sub(dsema->dsema_value, 1, m)

_os_atomic_c11_op(dsema->dsema_value, 1, m, sub, -)

_r = atomic_fetch_sub_explicit(dsema->dsema_value, 1),
等價(jià)于 dsema->dsema_value - 1

  • 進(jìn)入_dispatch_semaphore_wait_slow的源碼實(shí)現(xiàn)哥童,當(dāng)value小于0時(shí)挺份,根據(jù)等待事件timeout做出不同操作

dispatch_semaphore_signal 解鎖

該函數(shù)的源碼實(shí)現(xiàn)如下,其核心也是通過os_atomic_inc2o函數(shù)對value進(jìn)行了++操作贮懈,os_atomic_inc2o內(nèi)部是通過C++的atomic_fetch_add_explicit

  • 如果value 大于 0匀泊,表示操作無效,即執(zhí)行成功

  • 如果value 等于0朵你,則進(jìn)入長等待

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
    //signal 對 value是 ++
    long value = os_atomic_inc2o(dsema, dsema_value, release);
    if (likely(value > 0)) {//返回0各聘,表示當(dāng)前的執(zhí)行操作無效,相當(dāng)于執(zhí)行成功
        return 0;
    }
    if (unlikely(value == LONG_MIN)) {
        DISPATCH_CLIENT_CRASH(value,
                "Unbalanced call to dispatch_semaphore_signal()");
    }
    return _dispatch_semaphore_signal_slow(dsema);//進(jìn)入長等待
}

其中os_atomic_dec2o的宏定義轉(zhuǎn)換如下

os_atomic_inc2o(p, f, m) 
??
os_atomic_add2o(p, f, 1, m)
??
os_atomic_add(&(p)->f, (v), m)
??
_os_atomic_c11_op((p), (v), m, add, +)
??
({ _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_inc2o(dsema, dsema_value, release);

os_atomic_add2o(dsema, dsema_value, 1, m) 

os_atomic_add(&(dsema)->dsema_value, (1), m)

_os_atomic_c11_op((dsema->dsema_value), (1), m, add, +)

_r = atomic_fetch_add_explicit(dsema->dsema_value, 1),
等價(jià)于 dsema->dsema_value + 1

總結(jié)

  • dispatch_semaphore_create 主要就是初始化限號量

  • dispatch_semaphore_wait是對信號量的value進(jìn)行--抡医,即加鎖操作

  • dispatch_semaphore_signal 是對信號量的value進(jìn)行++躲因,即解鎖操作

所以,綜上所述忌傻,信號量相關(guān)函數(shù)的底層操作如圖所示

調(diào)度組

調(diào)度組的最直接作用是控制任務(wù)執(zhí)行順序大脉,常見方式如下

dispatch_group_create 創(chuàng)建組 
dispatch_group_async 進(jìn)組任務(wù) 
dispatch_group_notify 進(jìn)組任務(wù)執(zhí)行完畢通知 dispatch_group_wait 進(jìn)組任務(wù)執(zhí)行等待時(shí)間

//進(jìn)組和出組一般是成對使用的
dispatch_group_enter 進(jìn)組 
dispatch_group_leave 出組

使用

假設(shè)目前有兩個(gè)任務(wù),需要等待這兩個(gè)任務(wù)都執(zhí)行完畢水孩,才會(huì)更新UI箱靴,可以使用調(diào)度組

【修改一】如果將dispatch_group_notify移動(dòng)到最前面,能否執(zhí)行荷愕?

能執(zhí)行,但是是只要有enter-leave成對匹配棍矛,notify就會(huì)執(zhí)行安疗,不會(huì)等兩個(gè)組都執(zhí)行完。意思就是只要enter-leave成對就可以執(zhí)行

  • 【修改二】再加一個(gè)enter够委,即enter:wait 是 3:2荐类,能否執(zhí)行notify?

不能茁帽,會(huì)一直等待玉罐,等一個(gè)leave屈嗤,才會(huì)執(zhí)行notify

  • 【修改三】如果是 enter:wait 是 2:3,能否執(zhí)行notify吊输?

會(huì)崩潰饶号,因?yàn)閑nter-leave不成對,崩潰在里面是因?yàn)閍sync有延遲

dispatch_group_create 創(chuàng)建組

主要是創(chuàng)建group季蚂,并設(shè)置屬性茫船,此時(shí)的groupvalue0

  • 進(jìn)入dispatch_group_create源碼
dispatch_group_t
dispatch_group_create(void)
{
    return _dispatch_group_create_with_count(0);
}

  • 進(jìn)入_dispatch_group_create_with_count源碼,其中是對group對象屬性賦值扭屁,并返回group對象算谈,其中的n等于0
DISPATCH_ALWAYS_INLINE
static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n)
{
    //創(chuàng)建group對象,類型為OS_dispatch_group
    dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
            sizeof(struct dispatch_group_s));
    //group對象賦值
    dg->do_next = DISPATCH_OBJECT_LISTLESS;
    dg->do_targetq = _dispatch_get_default_queue(false);
    if (n) {
        os_atomic_store2o(dg, dg_bits,
                (uint32_t)-n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
        os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); // <rdar://22318411>
    }
    return dg;
}

dispatch_group_enter 進(jìn)組

進(jìn)入dispatch_group_enter源碼,通過os_atomic_sub_orig2odg->dg.bits--操作料滥,對數(shù)值進(jìn)行處理

void
dispatch_group_enter(dispatch_group_t dg)
{
    // The value is decremented on a 32bits wide atomic so that the carry
    // for the 0 -> -1 transition is not propagated to the upper 32bits.
    uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,//原子遞減 0 -> -1
            DISPATCH_GROUP_VALUE_INTERVAL, acquire);
    uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
    if (unlikely(old_value == 0)) {//如果old_value
        _dispatch_retain(dg); // <rdar://problem/22318411>
    }
    if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {//到達(dá)臨界值然眼,會(huì)報(bào)crash
        DISPATCH_CLIENT_CRASH(old_bits,
                "Too many nested calls to dispatch_group_enter()");
    }
}

dispatch_group_leave 出組

  • 進(jìn)入dispatch_group_leave源碼
    • -1 到 0,即++操作
    • 根據(jù)狀態(tài)葵腹,do-while循環(huán)高每,喚醒執(zhí)行block任務(wù)
    • 如果0 + 1 = 1,enter-leave不平衡礁蔗,即leave多次調(diào)用觉义,會(huì)crash
void
dispatch_group_leave(dispatch_group_t dg)
{
    // The value is incremented on a 64bits wide atomic so that the carry for
    // the -1 -> 0 transition increments the generation atomically.
    uint64_t new_state, old_state = os_atomic_add_orig2o(dg, dg_state,//原子遞增 ++
            DISPATCH_GROUP_VALUE_INTERVAL, release);
    uint32_t old_value = (uint32_t)(old_state & DISPATCH_GROUP_VALUE_MASK);
    //根據(jù)狀態(tài),喚醒
    if (unlikely(old_value == DISPATCH_GROUP_VALUE_1)) {
        old_state += DISPATCH_GROUP_VALUE_INTERVAL;
        do {
            new_state = old_state;
            if ((old_state & DISPATCH_GROUP_VALUE_MASK) == 0) {
                new_state &= ~DISPATCH_GROUP_HAS_WAITERS;
                new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
            } else {
                // If the group was entered again since the atomic_add above,
                // we can't clear the waiters bit anymore as we don't know for
                // which generation the waiters are for
                new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
            }
            if (old_state == new_state) break;
        } while (unlikely(!os_atomic_cmpxchgv2o(dg, dg_state,
                old_state, new_state, &old_state, relaxed)));
        return _dispatch_group_wake(dg, old_state, true);//喚醒
    }
    //-1 -> 0, 0+1 -> 1浴井,即多次leave晒骇,會(huì)報(bào)crash,簡單來說就是enter-leave不平衡
    if (unlikely(old_value == 0)) {
        DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
                "Unbalanced call to dispatch_group_leave()");
    }
}

  • 進(jìn)入_dispatch_group_wake源碼磺浙,do-while 循環(huán)進(jìn)行異步命中洪囤,調(diào)用_dispatch_continuation_async執(zhí)行
DISPATCH_NOINLINE
static void
_dispatch_group_wake(dispatch_group_t dg, uint64_t dg_state, bool needs_release)
{
    uint16_t refs = needs_release ? 1 : 0; // <rdar://problem/22318411>

    if (dg_state & DISPATCH_GROUP_HAS_NOTIFS) {
        dispatch_continuation_t dc, next_dc, tail;

        // Snapshot before anything is notified/woken <rdar://problem/8554546>
        dc = os_mpsc_capture_snapshot(os_mpsc(dg, dg_notify), &tail);
        do {
            dispatch_queue_t dsn_queue = (dispatch_queue_t)dc->dc_data;
            next_dc = os_mpsc_pop_snapshot_head(dc, tail, do_next);
            _dispatch_continuation_async(dsn_queue, dc,
                    _dispatch_qos_from_pp(dc->dc_priority), dc->dc_flags);//block任務(wù)執(zhí)行
            _dispatch_release(dsn_queue);
        } while ((dc = next_dc));//do-while循環(huán),進(jìn)行異步任務(wù)的命中

        refs++;
    }

    if (dg_state & DISPATCH_GROUP_HAS_WAITERS) {
        _dispatch_wake_by_address(&dg->dg_gen);//地址釋放
    }

    if (refs) _dispatch_release_n(dg, refs);//引用釋放
}

  • 進(jìn)入_dispatch_continuation_async源碼
DISPATCH_ALWAYS_INLINE
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_invoke一樣撕氧,都是宏
}

這步與異步函數(shù)的block回調(diào)執(zhí)行是一致的瘤缩,這里不再作說明

dispatch_group_notify 通知

  • 進(jìn)入dispatch_group_notify源碼,如果old_state等于0伦泥,就可以進(jìn)行釋放了
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq,
        dispatch_continuation_t dsn)
{
    uint64_t old_state, new_state;
    dispatch_continuation_t prev;

    dsn->dc_data = dq;
    _dispatch_retain(dq);
    //獲取dg底層的狀態(tài)標(biāo)識碼剥啤,通過os_atomic_store2o獲取的值,即從dg的狀態(tài)碼 轉(zhuǎn)成了 os底層的state
    prev = os_mpsc_push_update_tail(os_mpsc(dg, dg_notify), dsn, do_next);
    if (os_mpsc_push_was_empty(prev)) _dispatch_retain(dg);
    os_mpsc_push_update_prev(os_mpsc(dg, dg_notify), prev, dsn, do_next);
    if (os_mpsc_push_was_empty(prev)) {
        os_atomic_rmw_loop2o(dg, dg_state, old_state, new_state, release, {
            new_state = old_state | DISPATCH_GROUP_HAS_NOTIFS;
            if ((uint32_t)old_state == 0) { //如果等于0不脯,則可以進(jìn)行釋放了
                os_atomic_rmw_loop_give_up({
                    return _dispatch_group_wake(dg, new_state, false);//喚醒
                });
            }
        });
    }
}

除了leave可以通過_dispatch_group_wake喚醒府怯,其中dispatch_group_notify也是可以喚醒的

  • 其中os_mpsc_push_update_tail是宏定義,用于獲取dg的狀態(tài)碼
#define os_mpsc_push_update_tail(Q, tail, _o_next)  ({ \
    os_mpsc_node_type(Q) _tl = (tail); \
    os_atomic_store2o(_tl, _o_next, NULL, relaxed); \
    os_atomic_xchg(_os_mpsc_tail Q, _tl, release); \
})

dispatch_group_async

  • 進(jìn)入dispatch_group_async 源碼防楷,主要是包裝任務(wù)異步處理任務(wù)
#ifdef __BLOCKS__
void
dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
        dispatch_block_t db)
{

    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
    dispatch_qos_t qos;
    //任務(wù)包裝器
    qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
    //處理任務(wù)
    _dispatch_continuation_group_async(dg, dq, dc, qos);
}
#endif

  • 進(jìn)入_dispatch_continuation_group_async源碼牺丙,主要是封裝了dispatch_group_enter進(jìn)組操作
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_group_async(dispatch_group_t dg, dispatch_queue_t dq,
        dispatch_continuation_t dc, dispatch_qos_t qos)
{
    dispatch_group_enter(dg);//進(jìn)組
    dc->dc_data = dg;
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);//異步操作
}

  • 進(jìn)入_dispatch_continuation_async源碼,執(zhí)行常規(guī)的異步函數(shù)底層操作复局。既然有了enter冲簿,肯定有l(wèi)eave粟判,我們猜測block執(zhí)行之后隱性的執(zhí)行l(wèi)eave,通過斷點(diǎn)調(diào)試峦剔,打印堆棧信息

  • 搜索_dispatch_client_callout的調(diào)用叛溢,在_dispatch_continuation_with_group_invoke

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_continuation_with_group_invoke(dispatch_continuation_t dc)
{
    struct dispatch_object_s *dou = dc->dc_data;
    unsigned long type = dx_type(dou);
    if (type == DISPATCH_GROUP_TYPE) {//如果是調(diào)度組類型
        _dispatch_client_callout(dc->dc_ctxt, dc->dc_func);//block回調(diào)
        _dispatch_trace_item_complete(dc);
        dispatch_group_leave((dispatch_group_t)dou);//出組
    } else {
        DISPATCH_INTERNAL_CRASH(dx_type(dou), "Unexpected object type");
    }

所以轻绞,完美的印證dispatch_group_async底層封裝的是enter-leave

總結(jié)

  • enter-leave只要成對就可以,不管遠(yuǎn)近

  • dispatch_group_enter在底層是通過C++函數(shù),對group的value進(jìn)行--操作(即0 -> -1)

  • dispatch_group_leave在底層是通過C++函數(shù)栋艳,對group的value進(jìn)行++操作(即-1 -> 0)

  • dispatch_group_notify在底層主要是判斷group的state是否等于0导俘,當(dāng)?shù)扔?時(shí)槐雾,就通知

  • block任務(wù)的喚醒巾遭,可以通過dispatch_group_leave,也可以通過dispatch_group_notify

  • dispatch_group_async 等同于enter - leave平道,其底層的實(shí)現(xiàn)就是enter-leave

所以綜上所述睹欲,調(diào)度組的底層分析流程如下圖所示

dispatch_source

簡述

dispatch_source基礎(chǔ)數(shù)據(jù)類型,用于協(xié)調(diào)特定底層系統(tǒng)事件的處理一屋。

dispatch_source替代了異步回調(diào)函數(shù)窘疮,來處理系統(tǒng)相關(guān)的事件,當(dāng)配置一個(gè)dispatch時(shí)冀墨,你需要指定監(jiān)測的事件闸衫、dispatch queue、以及處理事件的代碼(block或函數(shù))诽嘉。當(dāng)事件發(fā)生時(shí)蔚出,dispatch source會(huì)提交你的block或函數(shù)到指定的queue去執(zhí)行

使用 Dispatch Source 而不使用 dispatch_async唯一原因就是利用聯(lián)結(jié)的優(yōu)勢

聯(lián)結(jié)的大致流程為:在任一線程上調(diào)用它的一個(gè)函數(shù)dispatch_source_merge_data后虫腋,會(huì)執(zhí)行Dispatch Source事先定義好的句柄(可以把句柄簡單理解為一個(gè)block)骄酗,這個(gè)過程叫 Custom event ,用戶事件。是 dispatch source 支持處理的一種事件悦冀。

簡單來說:這種事件是由你調(diào)用 dispatch_source_merge_data 函數(shù)來向自己發(fā)出的信號趋翻。

句柄是一種指向指針的指針,它指向的就是一個(gè)類或者結(jié)構(gòu)盒蟆,它和系統(tǒng)有密切的關(guān)系踏烙,這當(dāng)中還有一個(gè)通用的句柄,就是HANDLE

  • 實(shí)例句柄 HINSTANCE
  • 位圖句柄 HBITMAP
  • 設(shè)備表句柄 HDC
  • 圖標(biāo)句柄 HICON

特點(diǎn)

  • 其CPU負(fù)荷非常小历等,金陵不占用資源

  • 聯(lián)結(jié)的優(yōu)勢

使用

  • 創(chuàng)建dispatch源
dispatch_source_t source = dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)

參數(shù) 說明
type dispatch源可處理的事件
handle 可以理解為句柄宙帝、索引或id,假如要監(jiān)聽進(jìn)程募闲,需要傳入進(jìn)程的ID
mask 可以理解為描述,提供更詳細(xì)的描述愿待,讓它知道具體要監(jiān)聽什么
queue 自定義源需要的一個(gè)隊(duì)列浩螺,用來處理所有的響應(yīng)句柄

Dispatch Source 種類

其中type的類型有以下幾種

種類 說明
DISPATCH_SOURCE_TYPE_DATA_ADD 自定義的事件靴患,變量增加
DISPATCH_SOURCE_TYPE_DATA_OR 自定義的事件,變量OR
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口發(fā)送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 內(nèi)存壓力 (注:iOS8后可用)
DISPATCH_SOURCE_TYPE_PROC 進(jìn)程監(jiān)聽,如進(jìn)程的退出要出、創(chuàng)建一個(gè)或更多的子線程鸳君、進(jìn)程收到UNIX信號
DISPATCH_SOURCE_TYPE_READ IO操作,如對文件的操作患蹂、socket操作的讀響應(yīng)
DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信號時(shí)響應(yīng)
DISPATCH_SOURCE_TYPE_TIMER 定時(shí)器
DISPATCH_SOURCE_TYPE_VNODE 文件狀態(tài)監(jiān)聽或颊,文件被刪除、移動(dòng)传于、重命名
DISPATCH_SOURCE_TYPE_WRITE IO操作囱挑,如對文件的操作、socket操作的寫響應(yīng)

注意:

  • DISPATCH_SOURCE_TYPE_DATA_ADD
    當(dāng)同一時(shí)間沼溜,一個(gè)事件的的觸發(fā)頻率很高平挑,那么Dispatch Source會(huì)將這些響應(yīng)以ADD的方式進(jìn)行累積,然后等系統(tǒng)空閑時(shí)最終處理系草,如果觸發(fā)頻率比較零散通熄,那么Dispatch Source會(huì)將這些事件分別響應(yīng)。

  • DISPATCH_SOURCE_TYPE_DATA_OR 和上面的一樣找都,是自定義的事件唇辨,但是它是以O(shè)R的方式進(jìn)行累積

常用函數(shù)

//掛起隊(duì)列
dispatch_suspend(queue) 

//分派源創(chuàng)建時(shí)默認(rèn)處于暫停狀態(tài),在分派源分派處理程序之前必須先恢復(fù)
dispatch_resume(source) 

//向分派源發(fā)送事件能耻,需要注意的是赏枚,不可以傳遞0值(事件不會(huì)被觸發(fā)),同樣也不可以傳遞負(fù)數(shù)嚎京。
dispatch_source_merge_data 

//設(shè)置響應(yīng)分派源事件的block嗡贺,在分派源指定的隊(duì)列上運(yùn)行
dispatch_source_set_event_handler 

//得到分派源的數(shù)據(jù)
dispatch_source_get_data 

//得到dispatch源創(chuàng)建,即調(diào)用dispatch_source_create的第二個(gè)參數(shù)
uintptr_t dispatch_source_get_handle(dispatch_source_t source); 

//得到dispatch源創(chuàng)建鞍帝,即調(diào)用dispatch_source_create的第三個(gè)參數(shù)
unsigned long dispatch_source_get_mask(dispatch_source_t source); 

////取消dispatch源的事件處理--即不再調(diào)用block诫睬。如果調(diào)用dispatch_suspend只是暫停dispatch源。
void dispatch_source_cancel(dispatch_source_t source); 

//檢測是否dispatch源被取消帕涌,如果返回非0值則表明dispatch源已經(jīng)被取消
long dispatch_source_testcancel(dispatch_source_t source); 

//dispatch源取消時(shí)調(diào)用的block摄凡,一般用于關(guān)閉文件或socket等,釋放相關(guān)資源
void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler); 

//可用于設(shè)置dispatch源啟動(dòng)時(shí)調(diào)用block蚓曼,調(diào)用完成后即釋放這個(gè)block亲澡。也可在dispatch源運(yùn)行當(dāng)中隨時(shí)調(diào)用這個(gè)函數(shù)。
void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler); 

使用場景

經(jīng)常用于驗(yàn)證碼倒計(jì)時(shí)纫版,因?yàn)閐ispatch_source不依賴于Runloop床绪,而是直接和底層內(nèi)核交互,準(zhǔn)確性更高。

- (void)use033{
    //倒計(jì)時(shí)時(shí)間
    __block int timeout = 3;

    //創(chuàng)建隊(duì)列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

    //創(chuàng)建timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);

    //設(shè)置1s觸發(fā)一次癞己,0s的誤差
    /*
     - source 分派源
     - start 數(shù)控制計(jì)時(shí)器第一次觸發(fā)的時(shí)刻膀斋。參數(shù)類型是 dispatch_time_t,這是一個(gè)opaque類型痹雅,我們不能直接操作它仰担。我們得需要 dispatch_time 和 dispatch_walltime 函數(shù)來創(chuàng)建它們。另外绩社,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用摔蓝。
     - interval 間隔時(shí)間
     - leeway 計(jì)時(shí)器觸發(fā)的精準(zhǔn)程度
     */
    dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);

     //觸發(fā)的事件
    dispatch_source_set_event_handler(timer, ^{
        //倒計(jì)時(shí)結(jié)束,關(guān)閉
        if (timeout <= 0) {
            //取消dispatch源
            dispatch_source_cancel(timer);
        }else{
            timeout--;

            dispatch_async(dispatch_get_main_queue(), ^{
                //更新主界面的操作
                NSLog(@"倒計(jì)時(shí) - %d", timeout);
            });
        }
    });

    //開始執(zhí)行dispatch源
    dispatch_resume(timer);
}

使用GCD中的dispatch_source實(shí)現(xiàn) 自定義倒計(jì)時(shí)按鈕

實(shí)現(xiàn)思路只要是繼承自UIButton愉耙,然后通過GCDdispatch_source 實(shí)現(xiàn)倒計(jì)時(shí)按鈕贮尉,以下是demo的下載地址

CJLCountDownButton- CJLCountDownButton

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市劲阎,隨后出現(xiàn)的幾起案子绘盟,更是在濱河造成了極大的恐慌,老刑警劉巖悯仙,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件龄毡,死亡現(xiàn)場離奇詭異,居然都是意外死亡锡垄,警方通過查閱死者的電腦和手機(jī)沦零,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來货岭,“玉大人路操,你說我怎么就攤上這事∏Ч幔” “怎么了屯仗?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長搔谴。 經(jīng)常有香客問我魁袜,道長,這世上最難降的妖魔是什么敦第? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任峰弹,我火速辦了婚禮,結(jié)果婚禮上芜果,老公的妹妹穿的比我還像新娘鞠呈。我一直安慰自己,他們只是感情好右钾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布蚁吝。 她就那樣靜靜地躺著旱爆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灭将。 梳的紋絲不亂的頭發(fā)上疼鸟,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音庙曙,去河邊找鬼。 笑死浩淘,一個(gè)胖子當(dāng)著我的面吹牛捌朴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播张抄,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼砂蔽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了署惯?” 一聲冷哼從身側(cè)響起左驾,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎极谊,沒想到半個(gè)月后诡右,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡轻猖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年帆吻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咙边。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猜煮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出败许,到底是詐尸還是另有隱情王带,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布市殷,位于F島的核電站愕撰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏被丧。R本人自食惡果不足惜盟戏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甥桂。 院中可真熱鬧柿究,春花似錦、人聲如沸黄选。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至貌夕,卻和暖如春律歼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背啡专。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工险毁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人们童。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓畔况,卻偏偏與公主長得像,于是被迫代替她去往敵國和親慧库。 傳聞我的和親對象是個(gè)殘疾皇子跷跪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

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