libdispatch-類型解密

概述

GCD是一套強大的多線程方案,提供了多種任務隊列來提高開發效率,通過閱讀libdispatch的源碼可以更好的理解GCD的工作流程,幫助我們設計更好的代碼

結構類型

libdispatch使用宏定義實現了大量的模板結構類型,除此之外還使用了unionenum結合的方式實現動態參數類型的靈活性:

  • queue_type:隊列類型,例如全局隊列
  • source_type:資源統稱,queue或者function都可以看做是一個資源
  • semaphore_type:信號類型,信號可以保證資源同時多線程競爭下的安全
  • continuation_type:派發任務會被封裝成dispatch_continuation_t,然后被壓入隊列中
enum {  
    _DISPATCH_CONTINUATION_TYPE     =    0x00000, // meta-type for continuations  
    _DISPATCH_QUEUE_TYPE            =    0x10000, // meta-type for queues  
    _DISPATCH_SOURCE_TYPE           =    0x20000, // meta-type for sources  
    _DISPATCH_SEMAPHORE_TYPE        =    0x30000, // meta-type for semaphores  
    _DISPATCH_ATTR_TYPE             = 0x10000000, // meta-type for attribute structures  
  
    DISPATCH_CONTINUATION_TYPE      = _DISPATCH_CONTINUATION_TYPE,  
  
    DISPATCH_QUEUE_ATTR_TYPE        = _DISPATCH_QUEUE_TYPE | _DISPATCH_ATTR_TYPE,  

    DISPATCH_QUEUE_TYPE             = 1 | _DISPATCH_QUEUE_TYPE,  
    DISPATCH_QUEUE_GLOBAL_TYPE      = 2 | _DISPATCH_QUEUE_TYPE,  
    DISPATCH_QUEUE_MGR_TYPE         = 3 | _DISPATCH_QUEUE_TYPE,  

    DISPATCH_SEMAPHORE_TYPE         = _DISPATCH_SEMAPHORE_TYPE,  
  
    DISPATCH_SOURCE_ATTR_TYPE       = _DISPATCH_SOURCE_TYPE | _DISPATCH_ATTR_TYPE,  
  
    DISPATCH_SOURCE_KEVENT_TYPE     = 1 | _DISPATCH_SOURCE_TYPE,  
};

對于libdispatch的結構體類型來說,都存在DISPATCH_STRUCT_HEADER(x)類型的變量。通過##拼接變量名的方式對不同的類型生成動態的參數變量

#define DISPATCH_STRUCT_HEADER(x) \
    _OS_OBJECT_HEADER( \
    const struct dispatch_##x##_vtable_s *do_vtable, \
    do_ref_cnt, \
    do_xref_cnt); \
    struct dispatch_##x##_s *volatile do_next; \
    struct dispatch_queue_s *do_targetq; \
    void *do_ctxt; \
    void *do_finalizer; \
    unsigned int do_suspend_cnt;

聯合體union保證了各變量享用同一個內存地址,這也意味著各變量是互斥的。通過聯合體結構,libdispatch實現了類型強制轉換的效果。另外,通過宏定義DISPATCH_DECL(name)來保證所有dispatch_xxx_t變量實際上是一個指向dispatch_xxx_s的指針:

typedef union {
    struct _os_object_s *_os_obj;
    struct dispatch_object_s *_do;
    struct dispatch_continuation_s *_dc;
    struct dispatch_queue_s *_dq;
    struct dispatch_queue_attr_s *_dqa;
    struct dispatch_group_s *_dg;
    struct dispatch_source_s *_ds;
    struct dispatch_source_attr_s *_dsa;
    struct dispatch_semaphore_s *_dsema;
    struct dispatch_data_s *_ddata;
    struct dispatch_io_s *_dchannel;
    struct dispatch_operation_s *_doperation;
    struct dispatch_disk_s *_ddisk;
} dispatch_object_t __attribute__((__transparent_union__));

#define DISPATCH_DECL(name) typedef struct name##_s *name##_t

一方面,union在使用時會分配一塊足夠大的內存(能夠容納任一一種類型),這意味著可以隨時更換存儲數據,同樣可能造成數據的破壞;另一方面,它讓C函數擁有了返回參數多樣化的靈活性。但是要記住不同的數據類型在生成自身的do_vtable也有不同的表現:

/// GCD數據類型vtable屬性初始化
DISPATCH_VTABLE_INSTANCE(semaphore,
    .do_type = DISPATCH_SEMAPHORE_TYPE,
    .do_kind = "semaphore",
    .do_dispose = _dispatch_semaphore_dispose,
    .do_debug = _dispatch_semaphore_debug,
);

DISPATCH_VTABLE_INSTANCE(group,
    .do_type = DISPATCH_GROUP_TYPE,
    .do_kind = "group",
    .do_dispose = _dispatch_semaphore_dispose,
    .do_debug = _dispatch_semaphore_debug,
);

DISPATCH_VTABLE_INSTANCE(queue,
    .do_type = DISPATCH_QUEUE_TYPE,
    .do_kind = "queue",
    .do_dispose = _dispatch_queue_dispose,
    .do_invoke = NULL,
    .do_probe = (void *)dummy_function_r0,
    .do_debug = dispatch_queue_debug,
);

DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_root, queue,
    .do_type = DISPATCH_QUEUE_GLOBAL_TYPE,
    .do_kind = "global-queue",
    .do_debug = dispatch_queue_debug,
    .do_probe = _dispatch_queue_probe_root,
);

DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_mgr, queue,
    .do_type = DISPATCH_QUEUE_MGR_TYPE,
    .do_kind = "mgr-queue",
    .do_invoke = _dispatch_mgr_thread,
    .do_debug = dispatch_queue_debug,
    .do_probe = _dispatch_mgr_wakeup,
);

DISPATCH_VTABLE_INSTANCE(queue_specific_queue,
    .do_type = DISPATCH_QUEUE_SPECIFIC_TYPE,
    .do_kind = "queue-context",
    .do_dispose = _dispatch_queue_specific_queue_dispose,
    .do_invoke = NULL,
    .do_probe = (void *)dummy_function_r0,
    .do_debug = (void *)dispatch_queue_debug,
);

DISPATCH_VTABLE_INSTANCE(queue_attr,
    .do_type = DISPATCH_QUEUE_ATTR_TYPE,
    .do_kind = "queue-attr",
);

DISPATCH_VTABLE_INSTANCE(source,
    .do_type = DISPATCH_SOURCE_KEVENT_TYPE,
    .do_kind = "kevent-source",
    .do_invoke = _dispatch_source_invoke,
    .do_dispose = _dispatch_source_dispose,
    .do_probe = _dispatch_source_probe,
    .do_debug = _dispatch_source_debug,
);

queue為例,其結構類型為dispatch_queue_s,鑒于源碼中使用了大量的宏定義增加屬性,下面的結構是替換一部分宏定義后的結構:

struct dispatch_queue_s {
    DISPATCH_STRUCT_HEADER(queue);

    uint32_t volatile dq_running; 
    uint32_t dq_width;
    struct dispatch_object_s *volatile dq_items_tail;
    struct dispatch_object_s *volatile dq_items_head; 
    unsigned long dq_serialnum; 
    dispatch_queue_t dq_specific_q;

    char dq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE];
    char _dq_pad[DISPATCH_QUEUE_CACHELINE_PAD];
};

一個線程任務隊列有這么幾個重要的屬性:

  • do_vtable
    虛擬表,采用類似于C++的模板方式定義了一個結構體vtable,主要存儲了結構類型相關的描述信息

  • do_targetq
    目標隊列,GCD允許我們將某個任務隊列指派到另外的任務隊列中執行。當do_targetq不為空時,async的實現有會所不同

  • dq_width
    最大并發數,串行/主線程的這個值是1

  • do_ctxt
    線程上下文,用來存儲線程池相關數據,比如用于線程掛起和喚醒的信號量、線程池尺寸等

    struct dispatch_root_queue_context_s {
        union {
            struct {
                unsigned int volatile dgq_pending;
                dispatch_semaphore_t dgq_thread_mediator;
                uint32_t dgq_thread_pool_size;
            };
            char _dgq_pad[DISPATCH_CACHELINE_SIZE];
        };
    };
    
  • do_suspend_cnt
    用作暫停標記,當大于等于2時表示任務為延時任務。在任務達到時會修改標記,然后喚醒隊列調度任務


派發任務會被包裝成dispatch_continuation_s結構體對象,同樣

#define DISPATCH_OBJ_ASYNC_BIT      0x1
#define DISPATCH_OBJ_BARRIER_BIT    0x2
#define DISPATCH_OBJ_GROUP_BIT      0x4
#define DISPATCH_OBJ_SYNC_SLOW_BIT  0x8

#define DISPATCH_CONTINUATION_HEADER(x) \
    _OS_OBJECT_HEADER( \
    const void *do_vtable, \
    do_ref_cnt, \
    do_xref_cnt); \
    struct dispatch_##x##_s *volatile do_next; \
    dispatch_function_t dc_func; \
    void *dc_ctxt; \
    void *dc_data; \
    void *dc_other;

struct dispatch_continuation_s {
    DISPATCH_CONTINUATION_HEADER(continuation);
};

一個dispatch_continuation_s變量不總是只包裝了單個任務,它被設置成復用機制,通過TSD的方式保證每個線程可以擁有一定數量的復用continuation,以此來減少不必要的內存分配開銷。

  • dc_func
    承擔執行任務的對象,宏定義_dispatch_client_callout最終會以dc_func(dc_ctxt)的方式回調

  • dc_ctxt
    存儲了continuation對象的上下文數據,同樣用于執行任務

  • do_vtable
    只有當do_vtable的值小于127時才表示變量是一個continuation,派發到主/串行隊列的任務會被標記DISPATCH_OBJ_BARRIER_BIT屏障標記


libdispatch的結構對象都擁有自己的一張do_vtable虛擬表,同樣采用模板式的方式生成,每一個具體的結構類型會生成一張對應類型的虛擬表,但是屬性基本是統一的

#define DISPATCH_VTABLE_HEADER(x) \
    unsigned long const do_type; \
    const char *const do_kind; \
    size_t (*const do_debug)(struct dispatch_##x##_s *, char *, size_t); \
    struct dispatch_queue_s *(*const do_invoke)(struct dispatch_##x##_s *); \
    bool (*const do_probe)(struct dispatch_##x##_s *); \
    void (*const do_dispose)(struct dispatch_##x##_s *)

虛擬表采用類型名拼接的方式生成不同類型的重載函數,由于libdispatch類型采用union結構,這兩者結合極大的保證了執行的靈活性

  • do_type
    數據的具體類型,詳見上文中的枚舉值

  • do_kind
    數據的類型描述字符串,比如全局隊列為global-queue

  • do_debug
    debug方法,用來獲取調試時需要的變量信息字符串

  • do_probe
    用于檢測傳入對象中的一些值是否滿足條件

  • do_invoke
    喚醒隊列的方法,全局隊列和主隊列此項為NULL


disaptch_root_queue_context_s存儲了線程運行過程中的上下文數據,默認情況下已經創建了多個全局的上下文對象

struct dispatch_root_queue_context_s {
    union {
        struct {
            unsigned int volatile dgq_pending;
#if DISPATCH_USE_PTHREAD_POOL
            dispatch_semaphore_t dgq_thread_mediator;
            uint32_t dgq_thread_pool_size;
#endif
        };
        char _dgq_pad[DISPATCH_CACHELINE_SIZE];
    };
};

上下文對象在執行任務的過程中存儲了線程信號信息以及可用線程池數量。

  • dgq_pending_dgq_pad
    當前版本源碼中未使用

  • dgq_thread_mediator
    用于判斷是否存在可用線程資源,如果存在返回1,否則后續將創建新線程執行任務

  • dgq_thread_pool_size
    線程池剩余可用數量,只有dgq_thread_mediator的查詢返回0并且此項大于0時會嘗試創建新線程用以執行任務

GCD創建了總共八個四種優先級的全局上下文對象,每個上下文最多可容納255個線程數量

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

dispatch_semaphore_s是性能稍次于自旋鎖的的信號量對象,用來保證資源使用的安全性。

struct dispatch_semaphore_s {
    DISPATCH_STRUCT_HEADER(semaphore);
    long dsema_value;
    long dsema_orig;
    size_t dsema_sent_ksignals;

    size_t dsema_group_waiters;
    struct dispatch_sema_notify_s *dsema_notify_head;
    struct dispatch_sema_notify_s *dsema_notify_tail;
};

相比其他的結構,信號量的內部要簡潔的多,主要使用的就三個屬性:

  • dsema_value
    當前信號值,當這個值小于0時無法訪問加鎖資源

  • dsema_orig
    初始化信號值,限制了同時訪問資源的線程數量

  • dsema_sent_ksignals
    由于mach信號可能會被意外喚醒,通過原子操作來避免虛假信號


通過一張圖表示上面提及的四種數據類型的關系:


線程私有變量

進程中的全局變量和靜態變量是所有線程都能訪問的共享變量,這意味著訪問這樣的數據需要昂貴的同步花銷,Thread-specific-Data線程私有數據機制讓每一個線程擁有私有的全局變量。libdispatch提供了四個pthread_key來存取這些數據,在初始化階段進行初始化:

DISPATCH_EXPORT DISPATCH_NOTHROW
void
libdispatch_init(void)
{
    dispatch_assert(DISPATCH_QUEUE_PRIORITY_COUNT == 4);
    dispatch_assert(DISPATCH_ROOT_QUEUE_COUNT == 8);

    dispatch_assert(DISPATCH_QUEUE_PRIORITY_LOW ==
            -DISPATCH_QUEUE_PRIORITY_HIGH);
    dispatch_assert(countof(_dispatch_root_queues) ==
            DISPATCH_ROOT_QUEUE_COUNT);
    dispatch_assert(countof(_dispatch_root_queue_contexts) ==
            DISPATCH_ROOT_QUEUE_COUNT);

    ......

    _dispatch_thread_key_create(&dispatch_queue_key, _dispatch_queue_cleanup);
    _dispatch_thread_key_create(&dispatch_sema4_key,
            (void (*)(void *))_dispatch_thread_semaphore_dispose);
    _dispatch_thread_key_create(&dispatch_cache_key, _dispatch_cache_cleanup);
    _dispatch_thread_key_create(&dispatch_io_key, NULL);
    _dispatch_thread_key_create(&dispatch_apply_key, NULL);
#if DISPATCH_PERF_MON
    _dispatch_thread_key_create(&dispatch_bcounter_key, NULL);
#endif
    ......
}

基于大概率使用的派發任務,libdispatch緩存了dispatch_continuation_s,采用復用模式的做法在每次async中嘗試去獲取空閑的continuation變量,通過兩個函數存取數據:

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

推薦閱讀更多精彩內容