概述
GCD
是一套強大的多線程方案,提供了多種任務隊列來提高開發效率,通過閱讀libdispatch
的源碼可以更好的理解GCD
的工作流程,幫助我們設計更好的代碼
結構類型
libdispatch
使用宏定義實現了大量的模板結構類型,除此之外還使用了union
和enum
結合的方式實現動態參數類型的靈活性:
-
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);