iOS GCD底層原理分析

libdispatch.dylib源碼地址https://opensource.apple.com/release/macos-1015.html

隊列創建

  • 在源碼中搜索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);
}
  • 搜索_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)
{
    if (!slowpath(dqa)) { // 如果是串行隊列
        dqa = _dispatch_get_default_queue_attr(); // 串行隊列的attr 取默認attr
    } else if (dqa->do_vtable != DISPATCH_VTABLE(queue_attr)) { // 并行隊列的 attr->do_vtable 應該等于 DISPATCH_VTABLE(queue_attr)
        DISPATCH_CLIENT_CRASH(dqa->do_vtable, "Invalid queue attribute");
    }

    // dqai 創建 -
    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
    
    //第一步:規范化參數,例如qos, overcommit, tq
    // dispatch_qos_t qos是優先級?
    dispatch_qos_t qos = _dispatch_priority_qos(dqa->dqa_qos_and_relpri);
    // 是否overcommit(即queue創建的線程數是否允許超過實際的CPU個數)
    _dispatch_queue_attr_overcommit_t overcommit = dqa->dqa_overcommit;
    if (overcommit != _dispatch_queue_attr_overcommit_unspecified && tq) { //
        if (tq->do_targetq) { // overcommit 的queue 必須是全局的
            DISPATCH_CLIENT_CRASH(tq, "Cannot specify both overcommit and "
                    "a non-global target queue");
        }
    }

    // 下面這些代碼,因為用戶創建的queue的tq一定為NULL,因此,只要關注tq == NULL的分支即可,我們刪除了其余分支
    if (!tq) { // 自己創建的queue,tq都是null
        tq = _dispatch_get_root_queue( // 在root queue里面去取一個合適的queue當做target queue
                qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, // 無論是用戶創建的串行還是并行隊列,其qos都沒有指定,因此,qos這里都取DISPATCH_QOS_DEFAULT
                overcommit == _dispatch_queue_attr_overcommit_enabled);
        if (slowpath(!tq)) { // 如果根據create queue是傳入的屬性無法獲取到對應的tq,crash
            DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
        }
    }

    ...
    
    //拼接隊列名稱
    // 根據不同的queue類型,設置vtable。vtable實現了SERIAL queue 和 CONCURRENT queue的行為差異。
    const void *vtable;
    dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
    if (dqai.dqai_concurrent) { // 并發
        // OS_dispatch_queue_concurrent
        vtable = DISPATCH_VTABLE(queue_concurrent);
    } else {// 串行
        vtable = DISPATCH_VTABLE(queue_serial);
    }
    
    ....
    
    //創建隊列,并初始化
    dispatch_lane_t dq = _dispatch_object_alloc(vtable,
            sizeof(struct dispatch_lane_s)); // alloc
    //根據dqai.dqai_concurrent的值,就能判斷隊列 是 串行 還是并發,對于并發隊列,其queue width是DISPATCH_QUEUE_WIDTH_MAX,而串行隊列其width是1
    _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
    //設置隊列label標識符
    dq->dq_label = label;//設置dq的名字
    dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos, dqai.dqai_relpri);//優先級處理
    
    ...
    
    //類似于類與元類的綁定,不是直接的繼承關系,而是類似于模型與模板的關系
    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方法傳入daq(隊列類型)創建_dispatch_queue_attr_to_info_t類型的對象dqai,用于存儲隊列的相關屬性信息

_dispatch_queue_attr_to_info

-【第二步】設置隊列的相關屬性,例如服務質量qos是否overcommit(隊列創建的線程數是否允許超過實際的CPU個數)、tq(自己創建的隊列tq一定為NULL)

-【第三步】DISPATCH_VTABLE宏定義拼接隊列名字,OS_dispatch_+隊列類型=隊列名字
- 串行隊列:OS_dispatch_queue_serial
- 并發隊列:OS_dispatch_queue_concurrent

#define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)

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

#define DISPATCH_CLASS(name) OS_dispatch_##name

-【第四步】初始化隊列dq(alloc init),dqai.dqai_concurrent 可以判斷隊列類型,DISPATCH_QUEUE_WIDTH_MAX==并發,1==串行
- 進入_dispatch_object_alloc -> _os_object_alloc_realized方法中,發現里面設置了isa指向,說明了隊列是對象

_os_object_alloc_realized

- 進入_dispatch_queue_init方法,初始化隊列相關屬性
_dispatch_queue_init

-【第五步】通過_dispatch_trace_queue_create處理創建好的dq隊列

_dispatch_trace_queue_create

- 進入_dispatch_introspection_queue_create_hook -> dispatch_introspection_queue_get_info -> _dispatch_introspection_lane_get_info方法,實現了一個模板隊列
_dispatch_introspection_lane_get_info

總結

  • dispatch_queue_create中隊列類型決定了下層dq_width的值(max=并發,1=串行
  • 隊列queue對象,通過alloc+init創建,在alloc中有個class(宏定義)指定isa的指向
  • 隊列在底層是通過模板創建的,類型是結構體dispatch_introspection_queue_s
    隊列創建分析

異步函數

進入dispatch_async的源碼實現

  • _dispatch_continuation_init:任務包裝函數
  • _dispatch_continuation_async:并發處理函數
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)//work 任務
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME;
    dispatch_qos_t qos;

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

_dispatch_continuation_init任務包裝

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);//拷貝任務

    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 - 異步回調
    if (dc_flags & DC_FLAG_CONSUME) {
        func = _dispatch_call_block_and_release;//回調函數賦值 - 同步回調
    }
    return _dispatch_continuation_init_f(dc, dqu, ctxt, func, flags, dc_flags);
}
  • 通過_dispatch_Block_copy拷貝任務
  • 通過_dispatch_Block_invoke封裝任務,是一個異步回調函數
  • 如果是同步,則回調函數賦值為_dispatch_call_block_and_release
  • 通過_dispatch_continuation_init_f方法賦值,將func任務保存在屬性中
    _dispatch_continuation_init_f

_dispatch_continuation_async并發處理

主要是執行block回調 ,進入_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一樣,都是宏
}
  • 關鍵代碼dx_push是個宏
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
  • dx_push根據隊列類型執行不同的函數

    dq_push

  • 進入_dispatch_root_queue_push -> _dispatch_root_queue_push_inline ->_dispatch_root_queue_poke -> _dispatch_root_queue_poke_slow源碼,

    • 通過_dispatch_root_queues_init注冊回調
    • 使用pthread_create方法通過do-while循環創建線程
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();//重點
    
    ...
    //do-while循環創建線程
    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

通過dispatch_once_f單例實現

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_root_queues_init(void)
{
    dispatch_once_f(&_dispatch_root_queues_pred, NULL, _dispatch_root_queues_init_once);
}
  • 進入_dispatch_root_queues_init_once源碼,發現內部不同事物的調用句柄都是_dispatch_worker_thread2

    _dispatch_root_queues_init_once

  • block的回調執行路徑是_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,可以通過bt打印堆棧信息,

    bt打印堆棧

總結

  • 將異步任務拷貝并封裝后,設置回調函數func
  • 通過dx_push遞歸,重定向到跟隊列,通過pthread_creat方法do-while循環創建線程,通過dx_invoke執行回調,dx_pushdx_invoke是一一對應的
    異步函數

同步函數

進入dispatch_sync源碼,發現底層是通過柵欄函數實現的

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);
}
  • 進入_dispatch_sync_f源碼

    _dispatch_sync_f

  • 進入_dispatch_sync_f_inline源碼,dq->dq_width == 1表示串行隊列

    • 柵欄函數_dispatch_barrier_sync_f
    • 死鎖_dispatch_sync_f_slow,如果存在相互等待的情況下會死鎖
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)) {//表示是串行隊列
        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);//處理當前信息
    _dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
            _dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));//block執行并釋放
}

_dispatch_sync_f_slow死鎖

  • 進入_dispatch_sync_f_slow,當前主隊列掛起、阻塞

    _dispatch_sync_f_slow

  • 向隊列中添加任務,push加入主隊列,_dispatch_trace_item_push

    _dispatch_trace_item_push

  • 進入__DISPATCH_WAIT_FOR_QUEUE__,判斷dqu.dq是否為正在等待的主隊列,然后將dq的狀態和當前任務依賴的隊列進行匹配

    __DISPATCH_WAIT_FOR_QUEUE__

  • 進入_dq_state_drain_locked_by -> _dispatch_lock_is_locked_by源碼,如果當前等待的隊列和正在執行任務的是同一個隊列,即線程ID是否相同,如果相同會造成死鎖

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 &任何數都為0
    //即判斷 當前要等待的任務 和 正在執行的任務是否一樣,通俗的解釋就是 執行和等待的是否在同一隊列
    return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}

總結

  • 同步函數底層實現是同步柵欄函數
  • 如果正在執行任務的隊列和等待的是同一個隊列,會造成相互等待的局面,形成死鎖
同步函數流程圖

單例

進入dispatch_once源碼實現,發現底層是通過dispatch_once_f實現的

// val -- 靜態變量,由于不同位置定義的靜態變量是不同的,所以靜態變量具有唯一性
//block -- block回調
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
  • 進入dispatch_once_f源碼,其中的val是外界傳入的onceToken靜態變量,func_dispatch_Block_invoke(block)
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;
//os_atomic_load獲取當前任務的標識符
#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);//原子屬性關聯
    if (likely(v == DLOCK_ONCE_DONE)) {//已經執行過了,直接返回
        return;
    }
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    if (likely(DISPATCH_ONCE_IS_GEN(v))) {
    //任務執行后,加鎖失敗,重新存儲,將標識符== DLOCK_ONCE_DONE
        return _dispatch_once_mark_done_if_quiesced(l, v);
    }
#endif
#endif
    if (_dispatch_once_gate_tryenter(l)) {
      //嘗試進入任務,解鎖,執行block回調
        return _dispatch_once_callout(l, ctxt, func);
    }
//如果此時有任務1正在執行,再次進來一個任務2,則`任務2`進入無限次等待
    return _dispatch_once_wait(l);
}

_dispatch_once_gate_tryenter解鎖

底層通過os_atomic_cmpxchg方法進行標識符對比,如果對比沒問題進行解鎖,即任務的標識符置為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);//首先對比,然后進行改變
}

_dispatch_once_callout 回調

進入_dispatch_once_callout源碼

  • 【第一步】_dispatch_client_callout:block回調
  • 【第二步】_dispatch_once_gate_broadcast:廣播
DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
        dispatch_function_t func)
{
    _dispatch_client_callout(ctxt, func);//block調用執行
    _dispatch_once_gate_broadcast(l);//進行廣播:告訴別人有了歸屬,不要找我了
}
  • 進入_dispatch_client_callout源碼,其中f==_dispatch_Block_invoke(block),即異步回調
#undef _dispatch_client_callout
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
    @try {
        return f(ctxt);
    }
    @catch (...) {
        objc_terminate();
    }
}
  • 進入_dispatch_once_gate_broadcast -> _dispatch_once_mark_done,將任務的標識為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);
}

總結

  • 【只執行一次的原理】:GCD單例中有兩個參數onceToken 、block,其中onceToken是靜態變量,具有唯一性,在底層封裝成dispatch_once_gate_t類型的變量l變量l主要是用來獲取底層原子屬性關聯,即變量v,通過變量v來查詢任務狀態,如果變量v == DLOCK_ONCE_DONE,說明任務已經處理了,直接return

  • 【block調用時機】:如果任務沒有被執行,底層會通過C++函數的比較,任務狀態置為DLOCK_ONCE_UNLOCK,確保當前任務執行的唯一性,之后進行block回調,將任務置狀態為DLOCK_ONCE_DONE,確保下次進來不會執行

  • 【多線程影響】:如果在當前任務執行期間,有其他任務進來,會進入無限次等待,原因是當前任務已經獲取了鎖,進行了加鎖,其他任務是無法獲取鎖的

單例原理流程圖

柵欄函數

  • 控制任務執行順序,可以讓其同步執行
  • 只能控制同一并發隊列,只會阻塞一次
  • 同步柵欄添加進隊列時,當前線程會被加鎖,直到同步柵欄之前的任務和同步柵欄本身的任務都執行完畢,當前線程才會解鎖
  • 只有使用自定義隊列才有意義,如果使用串行隊列或者全局并發隊列,這個柵欄函數的作用==同步函數,沒有任何意義,還會耗費更多性能
  • dispatch_barrier_sync 同步柵欄函數阻塞線程,影響主線程任務執行
  • dispatch_barrier_async 異步柵欄函數阻塞的是隊列且必須是自定義的并發隊列,不影響主線程任務的執行
同步柵欄函數
異步柵欄函數

異步柵欄函數 底層分析

進入dispatch_barrier_async源碼,發現底層和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

同步柵欄函數 底層分析

進入dispatch_barrier_sync源碼

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

進入_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,即線程的唯一標識
    
    ...
    
    //判斷線程狀態,需不需要等待,是否回收
    if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {//柵欄函數也會死鎖
        return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,//沒有回收
                DC_FLAG_BARRIER | dc_flags);
    }
    //驗證target是否存在,如果存在,加入柵欄函數的遞歸查找 是否等待
    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)));//執行
}

主要分為以下幾步

  • 通過_dispatch_tid_self 獲取線程ID

  • 通過_dispatch_queue_try_acquire_barrier_sync判斷線程狀態

    _dispatch_queue_try_acquire_barrier_sync

    • 進入_dispatch_queue_try_acquire_barrier_sync_and_suspend源碼,在這里釋放
      _dispatch_queue_try_acquire_barrier_sync_and_suspend
  • 通過_dispatch_sync_recurse遞歸查找柵欄函數的target

  • 通過_dispatch_introspection_sync_begin向前信息進行處理

  • 通過_dispatch_lane_barrier_sync_invoke_and_complete執行block并釋放

    _dispatch_lane_barrier_sync_invoke_and_complete

信號量

  • 使任務同步執行,類似互斥鎖
  • 控制GCD最大并發數
dispatch_semaphore_create 創建

初始化信號量,設置GCD最大并發數且最大并發數必須大于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 加鎖

主要作用是對信號量dsema 通過os_atomic_dec2o進行--操作,內部執行C++函數atomic_fetch_sub_explicit

  • value >= 0,執行成功
  • value == LONG_MIN,系統拋出crash
  • value < 0,進入長等待
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
    // dsema_value 進行 -- 操作
    long value = os_atomic_dec2o(dsema, dsema_value, acquire);
    if (likely(value >= 0)) {//表示執行操作無效,即執行成功
        return 0;
    }
    return _dispatch_semaphore_wait_slow(dsema, timeout);//長等待
}
  • 進入_dispatch_semaphore_wait_slow源碼,當value<0時,更加等待事件timeout做出不同操作
    _dispatch_semaphore_wait_slow
dispatch_semaphore_signal解鎖

通過os_atomic_inc2o函數對value進行了++操作,os_atomic_inc2o內部是通過C++的atomic_fetch_add_explicit

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

總結

  • dispatch_semaphore_create 初始化信號量,設置最大并發數
  • dispatch_semaphore_wait 對信號量value--,加鎖
  • dispatch_semaphore_signal 對信號量value++,解鎖
    信號量流程圖

調度組

  • 控制任務執行順序
dispatch_group_create 創建組 
dispatch_group_async 進組任務 
dispatch_group_notify 進組任務執行完畢通知 dispatch_group_wait 進組任務執行等待時間

//進組和出組一般是成對使用的
dispatch_group_enter 進組 
dispatch_group_leave 出組
dispatch_group_create 創建組
  • 進入dispatch_group_create源碼
dispatch_group_t
dispatch_group_create(void)
{
    return _dispatch_group_create_with_count(0);
}
  • 進入_dispatch_group_create_with_count源碼,對group對象屬性賦值,并返回group
DISPATCH_ALWAYS_INLINE
static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n)
{
    //創建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 進組

進入dispatch_group_enter源碼,通過os_atomic_sub_orig2odg->dg.bits--操作,對數值進行處理

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)) {//到達臨界值,會報crash
        DISPATCH_CLIENT_CRASH(old_bits,
                "Too many nested calls to dispatch_group_enter()");
    }
}
dispatch_group_leave 出組

進入dispatch_group_leave源碼,通過os_atomic_add_orig2o對dg-> dg_state 作 `++操作

  • -1到0,++
  • 根據狀態,do_while循環,喚醒block執行任務
  • 如果0 + 1 = 1,enter-leave不平衡,即leave多次調用,會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);
    //根據狀態,喚醒
    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,會報crash,簡單來說就是enter-leave不平衡
    if (unlikely(old_value == 0)) {
        DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
                "Unbalanced call to dispatch_group_leave()");
    }
}
  • 進入_dispatch_group_wake源碼,do_while循環進行一步命中,調用_dispatch_continuation_async
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任務執行
            _dispatch_release(dsn_queue);
        } while ((dc = next_dc));//do-while循環,進行異步任務的命中

        refs++;
    }

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

    if (refs) _dispatch_release_n(dg, refs);//引用釋放
}
  • 進入_dispatch_continuation_async源碼,與異步函數的block回調一致
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一樣,都是宏
}
dispatch_group_notify 通知

進入dispatch_group_notify源碼,如果old_state==0,就可以進行釋放了,

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底層的狀態標識碼,通過os_atomic_store2o獲取的值,即從dg的狀態碼 轉成了 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,則可以進行釋放了
                os_atomic_rmw_loop_give_up({
                    return _dispatch_group_wake(dg, new_state, false);//喚醒
                });
            }
        });
    }
}
dispatch_group_async

進入dispatch_group_async源碼,包裝任務和異步處理任務,底層實際就是enter-leave

#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;
    //任務包裝器
    qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
    //處理任務
    _dispatch_continuation_group_async(dg, dq, dc, qos);
}
#endif
  • 進入_dispatch_continuation_group_async源碼,封裝了dispatch_group_enter進組操作
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);//進組
    dc->dc_data = dg;
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);//異步操作
}
  • 搜索_dispatch_client_callout的調用,在_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) {//如果是調度組類型
        _dispatch_client_callout(dc->dc_ctxt, dc->dc_func);//block回調
        _dispatch_trace_item_complete(dc);
        dispatch_group_leave((dispatch_group_t)dou);//出組
    } else {
        DISPATCH_INTERNAL_CRASH(dx_type(dou), "Unexpected object type");
    }

總結

  • enter-leave必須成對出現
  • dispatch_group_enter底層通過C++函數,對group的value進行--操作(0->-1)
  • dispatch_group_leave底層通過C++函數,對group的value進行++操作(-1 ->0)
  • dispatch_group_notify底層通過判斷group的state是否等于0,當==0時來進行通知
  • block中任務的喚醒可以通過dispatch_group_leavedispatch_group_notify
  • dispatch_group_async底層得實現就是enter-leave
    調度組原理圖

dispatch_source

  • 基礎數據類型,用于協調特定底層系統事件的處理

  • 代替異步回調函數,來處理系統相關的事件,當配置一個dispatch時,需要制定檢測的事件,dispatch queue處理事件的代碼(block或函數),當事件發生時,dispatch source會提交你的block或函數到queue中執行

  • 相對于dispatch_async的優勢是聯結和CPU負荷小,簡單來說就是由你調用dispatch_source_merge_data函數來向自己發送信號

    • 聯結:在任一線程上調度它的一個函數dispatch_source_merge_data,就會執行dispatch source事先定義好的句柄(block),這個過程叫做Custom event ,用戶事件
    • 句柄:一種指向指針的指針,指向的就是一個類或者結構,和系統有密切關系
      • 實例句柄 HINSTANCE
      • 位圖句柄 HBITMAP
      • 設備表句柄 HDC
      • 圖標句柄 HICON

使用

  • type :dispatch處理的事件
  • handle :可以理解為句柄、索引或id,假如要監聽進程,需要傳入進程的ID
  • mask :可以理解為描述,提供更詳細的描述,讓它知道具體要監聽什么
  • queue :自定義源需要的一個隊列,用來處理所有的響應句柄
dispatch_source_t source = dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)

Dispatch Source 種類

  • DISPATCH_SOURCE_TYPE_DATA_ADD:自定義的事件,變量增加,當同一時間,一個事件的的觸發頻率很高,那么Dispatch Source會將這些響應以ADD的方式進行累積,然后等系統空閑時最終處理,如果觸發頻率比較零散,那么Dispatch Source會將這些事件分別響應。
  • DISPATCH_SOURCE_TYPE_DATA_OR:自定義的事件,和DISPATCH_SOURCE_TYPE_DATA_ADD一樣,但是是以OR的方式進行累積
  • DISPATCH_SOURCE_TYPE_MACH_SEND:MACH端口發送
  • DISPATCH_SOURCE_TYPE_MACH_RECV:MACH端口接收
  • DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:內存壓力 (注:iOS8后可用)
  • DISPATCH_SOURCE_TYPE_PROC:進程監聽,如進程的退出、創建一個或更多的子線程、進程收到UNIX信號
  • DISPATCH_SOURCE_TYPE_READ:IO操作,如對文件的操作、socket操作的讀響應
  • DISPATCH_SOURCE_TYPE_SIGNAL:接收到UNIX信號時響應
  • DISPATCH_SOURCE_TYPE_TIMER:定時器
  • DISPATCH_SOURCE_TYPE_VNODE:文件狀態監聽,文件被刪除、移動、重命名
  • DISPATCH_SOURCE_TYPE_WRITE :IO操作,如對文件的操作、socket操作的寫響應

常見函數

//掛起隊列
dispatch_suspend(queue) 

//分派源創建時默認處于暫停狀態,在分派源分派處理程序之前必須先恢復
dispatch_resume(source) 

//向分派源發送事件,需要注意的是,不可以傳遞0值(事件不會被觸發),同樣也不可以傳遞負數。
dispatch_source_merge_data 

//設置響應分派源事件的block,在分派源指定的隊列上運行
dispatch_source_set_event_handler 

//得到分派源的數據
dispatch_source_get_data 

//得到dispatch源創建,即調用dispatch_source_create的第二個參數
uintptr_t dispatch_source_get_handle(dispatch_source_t source); 

//得到dispatch源創建,即調用dispatch_source_create的第三個參數
unsigned long dispatch_source_get_mask(dispatch_source_t source); 

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

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

//dispatch源取消時調用的block,一般用于關閉文件或socket等,釋放相關資源
void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler); 

//可用于設置dispatch源啟動時調用block,調用完成后即釋放這個block。也可在dispatch源運行當中隨時調用這個函數。
void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler); 

使用場景

用于驗證碼倒計時,因為dispatch_source不依賴于Runloop,而是直接和底層內核交互,準確性更高。

- (void)use033{
    //倒計時時間
    __block int timeout = 3;
    
    //創建隊列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    //創建timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);
    
    //設置1s觸發一次,0s的誤差
    /*
     - source 分派源
     - start 數控制計時器第一次觸發的時刻。參數類型是 dispatch_time_t,這是一個opaque類型,我們不能直接操作它。我們得需要 dispatch_time 和 dispatch_walltime 函數來創建它們。另外,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。
     - interval 間隔時間
     - leeway 計時器觸發的精準程度
     */
    dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
    
     //觸發的事件
    dispatch_source_set_event_handler(timer, ^{
        //倒計時結束,關閉
        if (timeout <= 0) {
            //取消dispatch源
            dispatch_source_cancel(timer);
        }else{
            timeout--;
            
            dispatch_async(dispatch_get_main_queue(), ^{
                //更新主界面的操作
                NSLog(@"倒計時 - %d", timeout);
            });
        }
    });
    
    //開始執行dispatch源
    dispatch_resume(timer);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容