iOS-底層原理25:GCD底層原理分析

在上一篇文章iOS-底層原理24:GCD 之 函數與隊列中從函數和隊列去認識GCD,本文將更深入的通過源碼去了解GCD底層原理。

1 查找GCD源碼

step1:用一個案例去尋找GCD的源碼,在此處打上斷點

dispatch_queue_t conque = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conque, ^{
    NSLog(@"異步函數");
});

等程序運行到此處,Debug --> Debug Workflow 勾上Always show Disassembly

step2: 程序會自動跳轉到匯編代碼

可以看到一些熟悉的符號

step3: 給程序打上符號斷點

step4: 繼續運行,發現程序程序自動跳轉到匯編

已經定位到對應的庫libdispatch

step5:蘋果開源地址搜索libdispatch

找到一個相關的庫將它下載下來

2 隊列

隊列類型: dispatch_queue_t
隊列的創建方法:dispatch_queue_create

2.1 隊列類型 dispatch_queue_t

step1: 查找dispatch_queue_t的定義

typedef struct dispatch_queue_s *dispatch_queue_t; 

可以看到dispatch_queue_t本身只是dispatch_queue_s這個結構體指針

step2: 全局搜索dispatch_queue_s {,查找dispatch_queue_s定義

struct dispatch_queue_s {
    DISPATCH_QUEUE_CLASS_HEADER(queue, void *__dq_opaque1);
    /* 32bit hole on LP64 */
} DISPATCH_ATOMIC64_ALIGN;

step3: 查找DISPATCH_QUEUE_CLASS_HEADER

#define DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
    _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__); \
    /* LP64 global queue cacheline boundary */ \
    unsigned long dq_serialnum; \// queue的編號
    const char *dq_label; \   //標簽
    DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \
        const uint16_t dq_width, \
        const uint16_t __dq_opaque2 \
    ); \
    dispatch_priority_t dq_priority; \//優先級
    union { \
        struct dispatch_queue_specific_head_s *dq_specific_head; \
        struct dispatch_source_refs_s *ds_refs; \
        struct dispatch_timer_source_refs_s *ds_timer_refs; \
        struct dispatch_mach_recv_refs_s *dm_recv_refs; \
        struct dispatch_channel_callbacks_s const *dch_callbacks; \
    }; \
    int volatile dq_sref_cnt

在這個宏里我們找到相關的方法_DISPATCH_QUEUE_CLASS_HEADER(x, pointer_sized_field);

step4: 繼續展開搜索查看里面的內容如下:

// 展開_DISPATCH_QUEUE_CLASS_HEADER

#define _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
    DISPATCH_OBJECT_HEADER(x); \
    DISPATCH_UNION_LE(uint64_t volatile dq_state, \
            dispatch_lock dq_state_lock, \
            uint32_t dq_state_bits \
    ); \

// 持續展開DISPATCH_OBJECT_HEADER

#define DISPATCH_OBJECT_HEADER(x) \
    struct dispatch_object_s _as_do[0]; \
    _DISPATCH_OBJECT_HEADER(x)
    
// 進一步查看 _DISPATCH_OBJECT_HEADER

#define _DISPATCH_OBJECT_HEADER(x) \
    struct _os_object_s _as_os_obj[0]; \
    OS_OBJECT_STRUCT_HEADER(dispatch_##x); \  // 這個宏,可以理解為dispatch_object_s繼承自_os_object_s
    struct dispatch_##x##_s *volatile do_next; \
    struct dispatch_queue_s *do_targetq; \
    void *do_ctxt; \
    void *do_finalizer
    

step5: 查看OS_OBJECT_STRUCT_HEADER

#define OS_OBJECT_STRUCT_HEADER(x) \
    _OS_OBJECT_HEADER(\
    const void *_objc_isa, \
    do_ref_cnt, \
    do_xref_cnt); \
// 注意這個成員變量,后面將任務Push到隊列就是通過這個變量
    const struct x##_vtable_s *do_vtable
    

來到OS_OBJECT_STRUCT_HEADER之后,我們需要注意一個成員變量,記住這個成員變量名字叫做do_vtable

step6: 查看_OS_OBJECT_HEADER

// 進一步查看 _OS_OBJECT_HEADER

#define _OS_OBJECT_HEADER(isa, ref_cnt, xref_cnt) \
        isa; /* must be pointer-sized */ \ // isa指針
        int volatile ref_cnt; \ // gcd對象內部引用計數
        int volatile xref_cnt// gcd對象外部引用計數(內外部都要減到0時,對象會被釋放)

_OS_OBJECT_HEADER包含isa指針引用計數等信息。

【總結】

dispatch_queue_t 的本質是一個結構體指針對象,指向一個dispatch_queue_s 類型的結構體,里面包含了label(標簽)、priority(優先級)等一些信息。

GCD源碼中的數據結構為dispatch_object_t聯合抽象類

typedef union {
    struct _os_object_s *_os_obj;// 基類
    struct dispatch_object_s *_do;// 基類繼承os_object
    struct dispatch_queue_s *_dq;// 隊列結構
    struct dispatch_queue_attr_s *_dqa;// 隊列相關屬性
    struct dispatch_group_s *_dg;// group結構
    struct dispatch_source_s *_ds;
    struct dispatch_channel_s *_dch;
    struct dispatch_mach_s *_dm;
    struct dispatch_mach_msg_s *_dmsg;
    struct dispatch_semaphore_s *_dsema;// 信號量
    struct dispatch_data_s *_ddata;
    struct dispatch_io_s *_dchannel;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;

2.2 創建隊列 dispatch_queue_create

我們知道隊列的創建是通過dispatch_queue_create,讓我們看下它在源碼中是如何創建的

step1: 打開源碼,全局搜索dispatch_queue_create,在queue.c文件中找到源碼

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

label : 標簽,我們平時傳入隊列的名字
attr :我們知道創建隊列時, attr 屬性有三個值可選,nilDISPATCH_QUEUE_SERIAL(實際上就是 nil) 或 DISPATCH_QUEUE_CONCURRENT

step2: 搜索_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 創建 -
    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
    
    //第一步:規范化參數,例如qos, overcommit, tq
    ...
    
    //拼接隊列名稱
    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);
    }
    
    ....
    
    //創建隊列,并初始化
    dispatch_lane_t dq = _dispatch_object_alloc(vtable,
            sizeof(struct dispatch_lane_s)); // alloc
    //根據dqai.dqai_concurrent的值,就能判斷隊列 是 串行 還是并發
    _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;//label賦值
    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
}
2.2.1 _dispatch_lane_create_with_target 分析

part1: 全局搜索_dispatch_queue_attr_to_info

通過_dispatch_queue_attr_to_info方法傳入dqa(即隊列類型,串行、并發等)創建dispatch_queue_attr_info_t類型的對象dqai,用于存儲隊列的相關屬性信息

part2: 設置隊列相關聯的屬性,例如服務質量qos等

part3: 通過DISPATCH_VTABLE拼接隊列名稱,即vtable,其中DISPATCH_VTABLE是宏定義,如下所示,所以隊列的類型是通過OS_dispatch_+隊列類型(queue_concurrent or queue_serial)拼接而成的

//object_internal.h
#define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)
??
#define DISPATCH_OBJC_CLASS(name)   (&DISPATCH_CLASS_SYMBOL(name))
??
#define DISPATCH_CLASS_SYMBOL(name) OS_dispatch_##name##_class
??
#define DISPATCH_CLASS(name) OS_dispatch_##name

part4: 通過alloc+init初始化隊列,即dq,其中在_dispatch_queue_init傳參中根據dqai.dqai_concurrent的布爾值,就能判斷隊列 是 串行 還是并發,而 vtable表示隊列的類型,說明隊列也是對象

  • part4.1: 進入_dispatch_object_alloc --> _os_object_alloc_realized方法中設置了isa的指向,從這里可以驗證隊列也是對象的說法
  • part4.2: 進入_dispatch_queue_init方法,隊列類型是dispatch_queue_t,并設置隊列的相關屬性

part5: 通過_dispatch_trace_queue_create對創建的隊列進行處理,其中_dispatch_trace_queue_create_dispatch_introspection_queue_create封裝的宏定義,最后會返回處理過的_dq

  • part5.1: _dispatch_trace_queue_create
  • part5.2:
  • part5.3: 進入_dispatch_introspection_queue_create_hook --> dispatch_introspection_queue_get_info --> _dispatch_introspection_lane_get_info中可以看出,與我們自定義的類還是有所區別的,創建隊列在底層的實現是通過模板創建的

【總結】

  • 隊列queue 也是一個對象,也需要底層通過alloc + init 創建,并且在alloc中也有一個class,這個class是通過宏定義拼接而成,并且同時會指定isa的指向

  • 創建隊列在底層的處理是通過模板創建的,其類型是dispatch_introspection_queue_s結構體

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

3 函數 底層原理

主要是分析 異步函數dispatch_async 和 同步函數dispatch_sync

3.1 異步函數 dispatch_async

進入dispatch_async函數源碼

void
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_continuation_async:并發處理函數

3.1.1 _dispatch_continuation_init 任務包裝

進入_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);
}

主要有以下幾步:

step1: 通過_dispatch_Block_copy拷貝任務

step2: 通過_dispatch_Block_invoke封裝任務,其中_dispatch_Block_invoke是個宏定義,根據以上分析得知是異步回調

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

step3: 如果是同步的,則回調函數賦值為_dispatch_call_block_and_release

step4: 通過_dispatch_continuation_init_f方法將回調函數賦值,即f就是func,將其保存在屬性中

3.1.2 _dispatch_continuation_async 并發處理

這個函數中,主要是執行block回調

step1: 進入_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一樣,都是宏
}

step2: 關鍵代碼是dx_push(dqu._dq, dc, qos)dx_push宏定義,如下所示

#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)

step3: 查看dq_push需要根據隊列的類型,執行不同的函數

3.1.2.1 符號斷點調試執行函數

part1: 添加如下測試代碼,并打上斷點

dispatch_queue_t conque = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conque, ^{
    NSLog(@"異步函數");
});
截屏2021-01-15 上午11.08.07.png

part2:運行到斷點處,加上符號斷點_dispatch_lane_concurrent_push_dispatch_lane_push

part3: 繼續運行

跳轉到匯編代碼,走的是_dispatch_lane_concurrent_push

part3: 進入_dispatch_lane_concurrent_push函數源碼

DISPATCH_NOINLINE
void
_dispatch_lane_concurrent_push(dispatch_lane_t dq, dispatch_object_t dou,
        dispatch_qos_t qos)
{
    // <rdar://problem/24738102&24743140> reserving non barrier width
    // doesn't fail if only the ENQUEUED bit is set (unlike its barrier
    // width equivalent), so we have to check that this thread hasn't
    // enqueued anything ahead of this call or we can break ordering
    if (dq->dq_items_tail == NULL &&
            !_dispatch_object_is_waiter(dou) &&
            !_dispatch_object_is_barrier(dou) &&
            _dispatch_queue_try_acquire_async(dq)) {
        return _dispatch_continuation_redirect_push(dq, dou, qos);
    }

    _dispatch_lane_push(dq, dou, qos);
}

有兩個重要的函數_dispatch_continuation_redirect_push_dispatch_lane_push

part4:_dispatch_continuation_redirect_push 打上符號斷點,_dispatch_lane_push的符號斷點已經存在,繼續執行

走的是_dispatch_continuation_redirect_push

part5: 進入_dispatch_continuation_redirect_push源碼

DISPATCH_NOINLINE
static void
_dispatch_continuation_redirect_push(dispatch_lane_t dl,
        dispatch_object_t dou, dispatch_qos_t qos)
{
    if (likely(!_dispatch_object_is_redirection(dou))) {
        dou._dc = _dispatch_async_redirect_wrap(dl, dou);
    } else if (!dou._dc->dc_ctxt) {
        // find first queue in descending target queue order that has
        // an autorelease frequency set, and use that as the frequency for
        // this continuation.
        dou._dc->dc_ctxt = (void *)
        (uintptr_t)_dispatch_queue_autorelease_frequency(dl);
    }

    dispatch_queue_t dq = dl->do_targetq;
    if (!qos) qos = _dispatch_priority_qos(dq->dq_priority);
    dx_push(dq, dou, qos); //遞歸
}

發現又調用了dx_push,即遞歸了,綜合前面隊列創建時可知,隊列也是一個對象,有父類、根類,所以會遞歸執行到根類的方法

part6: 將根類_dispatch_root_queue_push打上符號斷點,來驗證猜想是否正確

從運行結果看,猜想是正確的,隊列是一個對象,遞歸會執行到根類

part7: 進入源碼_dispatch_root_queue_push --> _dispatch_root_queue_push_inline --> _dispatch_root_queue_poke --> _dispatch_root_queue_poke_slow,將_dispatch_root_queue_poke_slow打上符號斷點,繼續運行

part8: 進入_dispatch_root_queue_poke_slow源碼

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_queue_poke_slow源碼中主要有兩步操作:

  • 1、通過_dispatch_root_queues_init方法注冊回調

  • 2、通過do-while循環創建線程,使用pthread_create方法

3.1.2.2 _dispatch_root_queues_init

part1: 進入_dispatch_root_queues_init源碼

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_once_f是個單例(后面會對單例底層進行分析)

part2: 進入_dispatch_root_queues_init_once源碼

內部不同事務的調用句柄都是_dispatch_worker_thread2

part3: 可運行案例打印堆棧信息

_dispatch_root_queues_init回調路徑: _dispatch_worker_thread2 --> _dispatch_root_queue_drain --> _dispatch_async_redirect_invoke --> _dispatch_continuation_pop --> _dispatch_client_callout --> _dispatch_call_block_and_release

說明

單例的block回調異步函數的block回調不同

  • 單例的block回調中的func是_dispatch_Block_invoke(block)
  • 異步函數的block回調中的func是dispatch_call_block_and_release

總結

所以,綜上所述,異步函數的底層分析如下

*【準備工作】:首先,將異步任務拷貝并封裝,并設置回調函數func

*【block回調】:底層通過dx_push遞歸,會重定向到根隊列,然后通過pthread_creat創建線程,最后通過dx_invoke執行block回調(注意dx_push 和 dx_invoke 是成對的)

異步函數的底層分析流程如圖所示

3.2 同步函數 dispatch_sync

step1: 進入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);
}

step2: 進入_dispatch_sync_f源碼

DISPATCH_NOINLINE
static void
_dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func,
        uintptr_t dc_flags)
{
    _dispatch_sync_f_inline(dq, ctxt, func, dc_flags);
}

step3: 進入_dispatch_sync_f_inline源碼

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_inline源碼中有兩個重要的函數:

  • 柵欄_dispatch_barrier_sync_f(可以通過后文的柵欄函數底層分析解釋),可以得出同步函數的底層實現其實是同步柵欄函數
  • 死鎖_dispatch_sync_f_slow,如果存在相互等待的情況,就會造成死鎖
3.2.1 死鎖 _dispatch_sync_f_slow

part1: 進入_dispatch_sync_f_slow源碼

part2: 進入_dispatch_trace_item_push源碼

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_trace_item_push(dispatch_queue_class_t dqu, dispatch_object_t _tail)
{
    if (unlikely(DISPATCH_QUEUE_PUSH_ENABLED())) {
        _dispatch_trace_continuation(dqu._dq, _tail._do, DISPATCH_QUEUE_PUSH);
    }

    _dispatch_trace_item_push_inline(dqu._dq, _tail._do);
    _dispatch_introspection_queue_push(dqu, _tail);
}

part3:進入__DISPATCH_WAIT_FOR_QUEUE__,判斷dq是否為正在等待的隊列,然后給出一個狀態state,然后將dq的狀態和當前任務依賴的隊列進行匹配

DISPATCH_NOINLINE
static void
__DISPATCH_WAIT_FOR_QUEUE__(dispatch_sync_context_t dsc, dispatch_queue_t dq)
{
        // 判斷qd是否為正在等待的主隊列
    uint64_t dq_state = _dispatch_wait_prepare(dq);
    if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) {
        DISPATCH_CLIENT_CRASH((uintptr_t)dq_state,
                "dispatch_sync called on queue "
                "already owned by current thread");
    }

    // Blocks submitted to the main thread MUST run on the main thread, and
    // dispatch_async_and_wait also executes on the remote context rather than
    // the current thread.
    //
    // For both these cases we need to save the frame linkage for the sake of
    // _dispatch_async_and_wait_invoke
    _dispatch_thread_frame_save_state(&dsc->dsc_dtf);

    if (_dq_state_is_suspended(dq_state) ||
            _dq_state_is_base_anon(dq_state)) {
        dsc->dc_data = DISPATCH_WLH_ANON;
    } else if (_dq_state_is_base_wlh(dq_state)) {
        dsc->dc_data = (dispatch_wlh_t)dq;
    } else {
        _dispatch_wait_compute_wlh(upcast(dq)._dl, dsc);
    }

    if (dsc->dc_data == DISPATCH_WLH_ANON) {
        dsc->dsc_override_qos_floor = dsc->dsc_override_qos =
                (uint8_t)_dispatch_get_basepri_override_qos_floor();
        _dispatch_thread_event_init(&dsc->dsc_event);
    }
    dx_push(dq, dsc, _dispatch_qos_from_pp(dsc->dc_priority));
    _dispatch_trace_runtime_event(sync_wait, dq, 0);
    if (dsc->dc_data == DISPATCH_WLH_ANON) {
        _dispatch_thread_event_wait(&dsc->dsc_event); // acquire
    } else {
        _dispatch_event_loop_wait_for_ownership(dsc);
    }
    if (dsc->dc_data == DISPATCH_WLH_ANON) {
        _dispatch_thread_event_destroy(&dsc->dsc_event);
        // If _dispatch_sync_waiter_wake() gave this thread an override,
        // ensure that the root queue sees it.
        if (dsc->dsc_override_qos > dsc->dsc_override_qos_floor) {
            _dispatch_set_basepri_override_qos(dsc->dsc_override_qos);
        }
    }
}

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

如果當前等待的和正在執行的是同一個隊列,即判斷線程ID是否相乘,如果相等,則會造成死鎖

同步函數 + 并發隊列 順序執行的原因

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

  • 將任務壓入隊列: _dispatch_thread_frame_push
  • 執行任務的block回調: _dispatch_client_callout
  • 將任務出隊:_dispatch_thread_frame_pop

從實現中可以看出,是先將任務push隊列中,然后執行block回調,在將任務pop,所以任務是順序執行的。

總結

同步函數的底層實現如下:

  • 同步函數的底層實現實際是同步柵欄函數

  • 同步函數中如果當前正在執行的隊列和等待的是同一個隊列,形成相互等待的局面,則會造成死鎖

所以,綜上所述,同步函數的底層實現流程如圖所示

4 單例

在日常開發中,我們一般使用GCD的dispatch_once來創建單例,如下所示:

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

首先對于單例,我們需要了解兩點:

  • 執行一次的原因】單例的流程只執行一次,底層是如何控制的,即為什么只能執行一次?

  • block調用時機】單例的block是在什么時候進行調用的?

4.1 單例 底層分析

step1: dispatch_once有兩個參數:

  • 參數1:onceToken,它是一個靜態變量,由于不同位置定義的靜態變量是不同的,所以靜態變量具有唯一性

  • 參數2:block回調

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

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

step2: 進入dispatch_once_f源碼

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)) {//已經執行過了,直接返回
        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)) {//嘗試進入
        return _dispatch_once_callout(l, ctxt, func);
    }
    return _dispatch_once_wait(l);//無限次等待
}

其中的val 是外界傳入的onceToken靜態變量,而func_dispatch_Block_invoke(block),其中單例的底層主要分為以下幾步:

  1. val,也就是靜態變量轉換為dispatch_once_gate_t類型的變量l
  2. 通過os_atomic_load獲取此時的任務的標識符v
  • 如果v等于DLOCK_ONCE_DONE,表示任務已經執行過了,直接return

  • 如果任務執行后,加鎖失敗了,則走到_dispatch_once_mark_done_if_quiesced函數,再次進行存儲,將標識符置為DLOCK_ONCE_DONE

  • 反之,則通過_dispatch_once_gate_tryenter嘗試進入任務,即解鎖,然后執行_dispatch_once_callout執行block回調

  1. 如果此時有任務正在執行,再次進來一個任務2,則通過_dispatch_once_wait函數讓任務2進入無限次等待
4.1.1 _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);//首先對比,然后進行改變
}
4.1.2 _dispatch_once_callout 回調

step1: 進入_dispatch_once_callout

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);//進行廣播:告訴別人有了歸屬,不要找我了

主要就兩步:

  1. _dispatch_client_callout:block回調執行

  2. _dispatch_once_gate_broadcast:進行廣播

step2: 進入_dispatch_client_callout源碼,主要就是執行block回調,其中的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();
    }
}

step3: 進入 _dispatch_once_gate_broadcast --> _dispatch_once_mark_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);
}

主要就是給dgo->dgo_once一個值,然后將任務的標識符為DLOCK_ONCE_DONE,即解鎖

總結

針對單例的底層實現,主要說明如下:

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

block調用時機】:如果此時任務沒有執行過,則會在底層通過C++函數的比較,將任務進行加鎖,即任務狀態置為DLOCK_ONCE_UNLOCK,目的是為了保證當前任務執行的唯一性,防止在其他地方有多次定義。加鎖之后進行block回調函數的執行,執行完成后,將當前任務解鎖,將當前的任務狀態置為DLOCK_ONCE_DONE,在下次進來時,就不會在執行,會直接返回

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

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

5 柵欄函數

GCD中常用的柵欄函數,主要有兩種:

名稱 作用 缺點
同步柵欄函數dispatch_barrier_sync,在主線程中執行 前面的任務執行完畢才會來到這里 堵塞線程
異步柵欄函數dispatch_barrier_async 前面的任務執行完畢才會來到這里 堵塞隊列

柵欄函數最直接的作用就是:控制任務執行順序,使同步執行

柵欄函數需要注意一下幾點:

  • 柵欄函數只能控制同一并發隊列

  • 同步柵欄添加進入隊列的時候,當前線程會被鎖死直到同步柵欄之前的任務和同步柵欄任務本身執行完畢時,當前線程才會打開然后繼續執行下一句代碼。

  • 使用柵欄函數時,使用自定義隊列才有意義,如果用的是串行隊列或者系統提供的全局并發隊列,這個柵欄函數的作用等同于一個同步函數的作用,沒有任何意義

5.1 代碼調試

5.1.1 異步柵欄函數
  • 異步柵欄函數 不會阻塞主線程 ,異步 堵塞的是隊列
- (void)wbinterDemo1{
    
    dispatch_queue_t queue1 = dispatch_queue_create("com.lbh.com", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue1, ^{
        NSLog(@"1-%@",[NSThread currentThread]);
    });
    dispatch_async(queue1, ^{
//        sleep(1);
        NSLog(@"2-%@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue1, ^{
//        sleep(2);
        NSLog(@"3-%@",[NSThread currentThread]);
    });
    dispatch_async(queue1, ^{
        NSLog(@"4-%@",[NSThread currentThread]);
    });
}

運行結果

分析

5.1.2 同步柵欄函數
  • 同步柵欄函數 會堵塞主線程
- (void)wbinterDemo2{
    
    dispatch_queue_t queue1 = dispatch_queue_create("com.lbh.com", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue1, ^{
//        sleep(2);
        NSLog(@"1=%@=%@",[NSThread currentThread],[NSDate date]);
    });
    
    dispatch_async(queue1, ^{
        NSLog(@"2=%@=%@",[NSThread currentThread],[NSDate date]);
    });
    
    dispatch_barrier_sync(queue1, ^{
        NSLog(@"3=%@=%@",[NSThread currentThread],[NSDate date]);
    });
    
    dispatch_async(queue1, ^{
        
        NSLog(@"4=%@=%@",[NSThread currentThread],[NSDate date]);
    });
}

運行結果

5.2 使用問題

代碼
- (void)interDemo3
{
    dispatch_queue_t queue1 = dispatch_queue_create("com.lbh.com", DISPATCH_QUEUE_CONCURRENT);
    
    NSMutableArray *array = [NSMutableArray array];
    
    
    for (int i = 0; i < 10000; i++) {
        
        dispatch_async(queue1, ^{
            [array addObject:[NSString stringWithFormat:@"%d",i]];
        });
    }
}
運行結果

問題: 為什么會崩潰?

分析

objc源碼中找到addObject:源碼

- (id)addObject:anObject
{
    return [self insertObject:anObject at:numElements]; 
}

??

- (id)insertObject:anObject at:(unsigned)index
{
    register id *this, *last, *prev;
    if (! anObject) return nil;
    if (index > numElements)
        return nil;
    if ((numElements + 1) > maxElements) {
    volatile id *tempDataPtr;
    /* we double the capacity, also a good size for malloc */
    // 這里在數組超過一定的空間之后就進行了雙倍的擴容
    maxElements += maxElements + 1;
    // 這里數組tempDataPtr 進行了realloc操作  所以在多個線程同時訪問的時候就會出現問題
    tempDataPtr = (id *) realloc (dataPtr, DATASIZE(maxElements));
    dataPtr = (id*)tempDataPtr;
    }
    this = dataPtr + numElements;
    prev = this - 1;
    last = dataPtr + index;
    while (this > last) 
    *this-- = *prev--;
    *last = anObject;
    numElements++;
    return self;
}

可以看到,當數組的容量超過maxElements的時候就會maxElements += maxElements + 1;,并且進行realloc重新創建了一個新的數組的操作,在多線程的操作,如果數組添加的元素太多就會出現給舊數組添加元素的時候,舊的數組其實已經被替代的情況,這樣就出現了崩潰

解決方法

1、數組初始化時給足夠大的空間

2、利用柵欄函數

問題:為什么不能使用同步柵欄函數?

3、使用互斥鎖 @synchronized (self) {}

5.3 底層原理

5.3.1 異步柵欄函數 底層原理

進入dispatch_barrier_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_async的底層實現時,已經知道dispatch_async的本質其實就是dispatch_barrier_async,這里就不在進行分析

5.3.2 同步柵欄函數 底層原理

進入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);
}
5.3.2.1 _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)));//執行
}

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

part1: 通過_dispatch_tid_self獲取線程ID

part2: 通過_dispatch_queue_try_acquire_barrier_sync判斷線程狀態

part2.1: 進入_dispatch_queue_try_acquire_barrier_sync_and_suspend源碼

part3: 通過_dispatch_sync_recurse遞歸查找柵欄函數的target

part4: 通過_dispatch_introspection_sync_begin對向前信息進行處理

part5: 通過_dispatch_lane_barrier_sync_invoke_and_complete執行block并釋放

6 信號量 dispatch_semaphore_t

信號量的作用一般是用來使任務同步執行,類似于互斥鎖,用戶可以根據需要控制GCD最大并發數,一般是這樣使用的

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

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

6.1 底層原理

選擇幾個重要的函數,對其底層進行分析

6.1.1 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;
}
6.1.2 dispatch_semaphore_wait 加鎖

step1: 進入dispatch_semaphore_wait源碼

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);//長等待
}

該函數的源碼實現如下,其主要作用是對信號量dsema通過os_atomic_dec2o進行了--操作,其內部是執行的C++的atomic_fetch_sub_explicit方法

  • 如果value 大于等于0,表示操作無效,即執行成功

  • 如果value 等于LONG_MIN,系統會拋出一個crash

  • 如果value 小于0,則進入長等待

其中os_atomic_dec2o的宏定義轉換如下

#define os_atomic_inc2o(p, f, m) \
        os_atomic_add2o(p, f, 1, m)

??

#define os_atomic_add2o(p, f, v, m) \
        os_atomic_add(&(p)->f, (v), m)
??

#define os_atomic_add(p, v, m) \
        _os_atomic_c11_op((p), (v), m, add, +)

??
#define _os_atomic_c11_op(p, v, m, o, op) \
        ({ _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); })

將具體的值代入為

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

推薦閱讀更多精彩內容