概述
GCD
是一套強大的多執行緒方案,提供了多種任務佇列來提高開發效率,通過閱讀libdispatch
的原始碼可以更好的理解GCD
的工作流程,幫助我們設計更好的程式碼
結構型別
libdispatch
使用巨集定義實現了大量的模板結構型別,除此之外還使用了union
和enum
結合的方式實現動態引數型別的靈活性:
queue_type
:佇列型別,例如全域性佇列source_type
:資源統稱,queue
或者function
都可以看做是一個資源semaphore_type
:訊號型別,訊號可以保證資源同時多執行緒競爭下的安全continuation_type
:派發任務會被封裝成dispatch_continuation_t
,然後被壓入佇列中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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)
型別的變數。通過##
拼接變數名的方式對不同的型別生成動態的引數變數
1 2 3 4 5 6 7 8 9 10 |
#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
的指標:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
也有不同的表現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
/// 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
,鑑於原始碼中使用了大量的巨集定義增加屬性,下面的結構是替換一部分巨集定義後的結構:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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
執行緒上下文,用來儲存執行緒池相關資料,比如用於執行緒掛起和喚醒的訊號量、執行緒池尺寸等
12345678910struct 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
結構體物件,同樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#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
虛擬表,同樣採用模板式的方式生成,每一個具體的結構型別會生成一張對應型別的虛擬表,但是屬性基本是統一的
1 2 3 4 5 6 7 |
#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
儲存了執行緒執行過程中的上下文資料,預設情況下已經建立了多個全域性的上下文物件
1 2 3 4 5 6 7 8 9 10 11 12 |
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
個執行緒數量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#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
是效能稍次於自旋鎖的的訊號量物件,用來保證資源使用的安全性。
1 2 3 4 5 6 7 8 9 10 |
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
來存取這些資料,在初始化階段進行初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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
變數,通過兩個函式存取資料:
1 2 |
void* _Nullable pthread_getspecific(pthread_key_t); int pthread_setspecific(pthread_key_t , const void * _Nullable); |