GCD原始碼吐血分析(1)——GCD Queue
看了快半個月的GCD原始碼,只能說太變態了。
先來吐槽一下:一個函式,呼叫棧都是十幾層…… 為了效率,程式碼使用了純C語言,但是為了模擬面向物件中的繼承,虛擬函式等,定義了一層層的巨集定義,看一個struct的定義要繞過來繞過去…… 網上的資料極少,有的那幾篇,還都是用舊版本的GCD在說事兒,而新版的GCD原始碼複雜度及晦澀度,在原有的基礎上,又升級了一個檔次……
說實話,到現在也不敢說是看懂了或是看對了,所以這篇部落格只是個人的一個不負責任的總結而已。天哪,讓我從GCD的泥潭中出來吧……
基本的資料結構
GCD的類都是struct定義的。但並不像runtime一樣,直接使用:
GCD中的資料結構是如下組織的:
GCD中類都繼承自統一的基類dispatch_object_t
, 再下一層,則是表示系統物件的基類_os_object_s
。
root 基類dispatch_object_t
的定義如下:
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_mach_s *_dm;
struct dispatch_mach_msg_s *_dmsg;
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 DISPATCH_TRANSPARENT_UNION;
可見它是一個聯合,可以表示union中任意的型別。起到的作用和基類指標相似,所有的子型別都可以用dispatch_object_t
統一表示。
dispatch_queue_s
: GCD中的queue。
dispatch_continuation_s
: 我們向queue提交的任務,無論block還是function形式,最終都會被封裝為dispatch_continuation_s。
注意,這只是邏輯上的繼承關係,在實際的原始碼中,是看不到繼承的符號的。而是通過巨集的方式,將父類中的屬性,在子類中再寫一次。如繼承了_os_object_s
的dispatch_object_s
的定義:
struct dispatch_object_s {
_DISPATCH_OBJECT_HEADER(object);
};
#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
簡單的理解了GCD中基本類的關係,我們就來看一下各個類中的定義。
_os_object_s
typedef struct _os_object_s {
_OS_OBJECT_HEADER(
const _os_object_vtable_s *os_obj_isa,
os_obj_ref_cnt,
os_obj_xref_cnt);
} _os_object_s;
#define _OS_OBJECT_HEADER(isa, ref_cnt, xref_cnt) \
isa; /* must be pointer-sized */ \
int volatile ref_cnt; \
int volatile xref_cnt
把巨集展開,得到直觀的定義:
typedef struct _os_object_s {
const _os_object_vtable_s *os_obj_isa; // 這個也是個巨集定義,展開後似乎是可以被子類重寫的和引用計數相關的兩個函式指標
int volatile os_obj_ref_cnt; // 引用計數,這是內部gcd內部使用的計數器
int volatile os_obj_xref_cnt; // 外部引用計數,這是gcd外部使用的計數器,兩者都為0的時候才能dispose
} _os_object_s;
dispatch_object_s
struct dispatch_object_s {
_DISPATCH_OBJECT_HEADER(object);
};
#define _DISPATCH_OBJECT_HEADER(x) \
struct _os_object_s _as_os_obj[0]; \
OS_OBJECT_STRUCT_HEADER(dispatch_##x); \
struct dispatch_##x##_s *volatile do_next; \
struct dispatch_queue_s *do_targetq; \
void *do_ctxt; \
void *do_finalizer
巨集展開:
struct dispatch_object_s {
struct _os_object_s _as_os_obj[0];
OS_OBJECT_STRUCT_HEADER(dispatch_object); // 繼承自_os_object_s的部分,和引用計數相關
struct dispatch_object_s *volatile do_next; // 連結串列的 next
struct dispatch_queue_s *do_targetq; // 目標佇列,指定這個object在哪個queue中執行
void *do_ctxt; // 上下文,我們要傳遞的引數
void *do_finalizer; // 解構函式
};
dispatch_queue_s
struct dispatch_queue_s {
_DISPATCH_QUEUE_HEADER(queue);
} DISPATCH_ATOMIC64_ALIGN;
#define _DISPATCH_QUEUE_HEADER(x) \
struct os_mpsc_queue_s _as_oq[0]; \
DISPATCH_OBJECT_HEADER(x); \
_OS_MPSC_QUEUE_FIELDS(dq, dq_state); \
uint32_t dq_side_suspend_cnt; \
dispatch_unfair_lock_s dq_sidelock; \
union { \
dispatch_queue_t dq_specific_q; \
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; \
}; \
DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \
const uint16_t dq_width, \
const uint16_t __dq_opaque \
); \
巨集展開:
struct dispatch_queue_s {
struct os_mpsc_queue_s _as_oq[0];
DISPATCH_OBJECT_HEADER(x);
_OS_MPSC_QUEUE_FIELDS(dq, dq_state); // 這裡又是一個巨集,需要再展開
uint32_t dq_side_suspend_cnt;
dispatch_unfair_lock_s dq_sidelock;
union {
dispatch_queue_t dq_specific_q;
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;
};
DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags,
const uint16_t dq_width,
const uint16_t __dq_opaque
);
}
#define _OS_MPSC_QUEUE_FIELDS(ns, __state_field__) \
struct dispatch_object_s *volatile ns##_items_head; \
DISPATCH_UNION_LE(uint64_t volatile __state_field__, \
dispatch_lock __state_field__##_lock, \
uint32_t __state_field__##_bits \
) DISPATCH_ATOMIC64_ALIGN; \
/* LP64 global queue cacheline boundary */ \
unsigned long ns##_serialnum; \
const char *ns##_label; \
struct dispatch_object_s *volatile ns##_items_tail; \
dispatch_priority_t ns##_priority; \
int volatile ns##_sref_cnt
展開_OS_MPSC_QUEUE_FIELDS
後:
struct dispatch_queue_s {
struct os_mpsc_queue_s _as_oq[0];
// 展開_OS_MPSC_QUEUE_FIELDS
struct dispatch_object_s *volatile queue_items_head; // queue首元素
DISPATCH_UNION_LE(
uint64_t volatile dq_state, // queue的狀態
dispatch_lock dq_state_lock,
uint32_t dq_state_bits
) DISPATCH_ATOMIC64_ALIGN;
unsigned long queue_serialnum; // queue的編號
const char *queue_label; // queue的名稱
struct dispatch_object_s *volatile queue_items_tail; // queue 尾元素
dispatch_priority_t queue_priority; // queue優先順序
int volatile queue_sref_cnt
uint32_t dq_side_suspend_cnt;
dispatch_unfair_lock_s dq_sidelock;
union { // 這些是提交的queue上的元素佇列嗎????????
dispatch_queue_t dq_specific_q;
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;
};
DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags,
const uint16_t dq_width, // queue的併發數(dq_width==1表示是序列佇列)
const uint16_t __dq_opaque
);
}
dispatch_continuation_s
typedef struct dispatch_continuation_s {
struct dispatch_object_s _as_do[0];
DISPATCH_CONTINUATION_HEADER(continuation);
} *dispatch_continuation_t;
#define DISPATCH_CONTINUATION_HEADER(x) \
union { \
const void *do_vtable; \
uintptr_t dc_flags; \
}; \
union { \
pthread_priority_t dc_priority; \
int dc_cache_cnt; \
uintptr_t dc_pad; \
}; \
struct dispatch_##x##_s *volatile do_next; \
struct voucher_s *dc_voucher; \
dispatch_function_t dc_func; \
void *dc_ctxt; \
void *dc_data; \
void *dc_other
巨集展開後
typedef struct dispatch_continuation_s {
struct dispatch_object_s _as_do[0];
union {
const void *do_vtable;
uintptr_t dc_flags;
};
union {
pthread_priority_t dc_priority;
int dc_cache_cnt;
uintptr_t dc_pad;
};
struct dispatch_continuation_s *volatile do_next;
struct voucher_s *dc_voucher;
dispatch_function_t dc_func;
void *dc_ctxt;
void *dc_data;
void *dc_other
} *dispatch_continuation_t;
dispatch_queue_attr_s
struct dispatch_queue_attr_s {
OS_OBJECT_STRUCT_HEADER(dispatch_queue_attr);
dispatch_priority_requested_t dqa_qos_and_relpri; // queue優先順序
uint16_t dqa_overcommit:2; // 是否可以overcommit
uint16_t dqa_autorelease_frequency:2;
uint16_t dqa_concurrent:1; // 是否是併發佇列
uint16_t dqa_inactive:1; // 是否啟用
};
root queues
我們要使用GCD,需要把任務提交到佇列。而我們可以用如下三種方法之一獲取要使用的佇列:
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr)
dispatch_queue_t dispatch_get_main_queue(void)
dispatch_queue_t
dispatch_get_global_queue(long identifier, unsigned long flags)
第一種方法是建立一個queue,後兩種方法是獲取系統自定義的queue。
但其實背後的實現是,無論是自己建立還是獲取系統定義的queue,只會在GCD啟動時建立的root queue陣列中,取得一個queue而已。
使用者層面的queue和root queue之間的關係如下圖所示:
TODO
root queue一共有12個,分別有不同的優先順序和序列號。
讓我們一起看看它們的實現,首先,是dispatch_queue_create
。
dispatch_queue_create
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
return _dispatch_queue_create_with_target(label, attr,
DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
可以看到,當用戶要建立一個queue的時候,需要指定target queue,即建立的queue最終是在哪個queue上執行的,這稱之為target queue
。對於使用者建立的queue來說,這個target queue會取root queue之一。
這裡的DISPATCH_TARGET_QUEUE_DEFAULT
是一個巨集定義:
#define DISPATCH_TARGET_QUEUE_DEFAULT NULL
static dispatch_queue_t
_dispatch_queue_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");
}
//
// Step 1: Normalize arguments (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");
}
}
//
// Step 2: Initialize the queue
//
const void *vtable;
dispatch_queue_flags_t dqf = 0;
// 根據不同的queue型別,設定vtable。vtable實現了SERIAL queue 和 CONCURRENT queue的行為差異。
if (dqa->dqa_concurrent) { // 併發
vtable = DISPATCH_VTABLE(queue_concurrent);
} else { // 序列
vtable = DISPATCH_VTABLE(queue_serial);
}
// 建立一個與tq對應的dq,用來給使用者返回
dispatch_queue_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_queue_s) - DISPATCH_QUEUE_CACHELINE_PAD);
// 初始化dq,可以看到dqa->dqa_concurrent,對於併發佇列,其queue width是DISPATCH_QUEUE_WIDTH_MAX,而序列佇列其width是1
_dispatch_queue_init(dq, dqf, dqa->dqa_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqa->dqa_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
dq->dq_label = label; // 設定dq的名字
#if HAVE_PTHREAD_WORKQUEUE_QOS
dq->dq_priority = dqa->dqa_qos_and_relpri;
if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
}
#endif
_dispatch_retain(tq);
if (qos == QOS_CLASS_UNSPECIFIED) { // 如果沒有指定queue的優先順序,則預設繼承target queue的優先順序
// legacy way of inherithing the QoS from the target
_dispatch_queue_priority_inherit_from_target(dq, tq);
}
if (!dqa->dqa_inactive) {
_dispatch_queue_inherit_wlh_from_target(dq, tq);
}
dq->do_targetq = tq; // 這一步,很關鍵!!! 將root queue設定為dq的target queue,root queue和新建立的queue聯合在了一起
_dispatch_object_debug(dq, "%s", __func__);
// 將新建立的dq,新增到GCD內部管理的叫做_dispatch_introspection的queue列表中。這是GCD內部維護的一個queue列表,具體作用不太清楚。
return _dispatch_introspection_queue_create(dq);
}
去除多餘的判斷,其實邏輯也很簡單,當我們呼叫dispatch_queue_create
,GCD內部會呼叫_dispatch_queue_create_with_target
, 它首先會根據我們建立的queue的屬性:DISPATCH_QUEUE_SERIAL
或DISPATCH_QUEUE_CONCURRENT
,到root queue陣列
中取出一個對應的queue作為target queue
。然後,會新建一個dispatch_queue_t物件,並設定其target queue,返回給使用者。同時,在GCD內部,新建的queue還會被加入introspection queue列表
中。
這裡的關鍵是根據queue屬性獲取對應的target root queue:
tq = _dispatch_get_root_queue( // 在root queue裡面去取一個合適的queue當做target queue
qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, // 序列 用DISPATCH_QOS_DEFAULT, 並行 用自己的qos
_dispatch_get_root_queue
的實現如下:
static inline dispatch_queue_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
return &_dispatch_root_queues[2 * (qos - 1) + overcommit]; // 根據qos和 是否overcommit,取得root queues陣列中對應的queue(一共有12個root queue)
}
qos==DISPATCH_QOS_DEFAULT
,值是4。根據公式,2*(4-1) + 1 = 7, 所有建立的序列佇列應該取root queue陣列的第8個queue,而由於並行佇列的overcommit是false(0),並行佇列回去root queue的第7個元素:
// 使用者建立的並行佇列,預設會使用這個target queue
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_DEFAULTQUEUE,
.dq_label = "com.apple.root.default-qos",
.dq_serialnum = 10,
),
// 使用者建立的序列佇列,會使這個target queue, main queue 也會取這個值,但是會將 .dq_label = "com.apple.main-thread",
// .dq_atomic_flags = DQF_THREAD_BOUND | DQF_CANNOT_TRYSYNC | DQF_WIDTH(1),
// .dq_serialnum = 1,
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
DISPATCH_PRIORITY_FLAG_DEFAULTQUEUE | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.default-qos.overcommit",
.dq_serialnum = 11,
),
OK,現在我們已經知道使用者自建立的queue預設都會附加到root queue上。那麼對於dispatch_get_main_queue
, dispatch_get_global_queue
是否也有類似的邏輯呢?我們先來看dispatch_get_global_queue
。
dispatch_get_global_queue
我們通常會用如下程式碼,來獲取一個全域性的併發佇列,並指定其優先順序。
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
其實現如下:
dispatch_queue_t
dispatch_get_global_queue(long priority, unsigned long flags)
{
if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {
return DISPATCH_BAD_INPUT;
}
dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority); // 將使用者使用的優先順序轉換為root queue的優先順序
if (qos == DISPATCH_QOS_UNSPECIFIED) {
return DISPATCH_BAD_INPUT;
}
return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT); // 由於flags是保留值,均取0,因此global queue都是no overcommit的
}
邏輯很簡單,首先將外部傳入的queue優先順序轉換為GCD內部的優先順序dispatch_qos_t qos
。 然後,在呼叫_dispatch_get_root_queue
獲取root queue中對應的queue。
我們來看一下_dispatch_qos_from_queue_priority
的實現:
static inline dispatch_qos_t
_dispatch_qos_from_queue_priority(long priority)
{
switch (priority) {
case DISPATCH_QUEUE_PRIORITY_BACKGROUND: return DISPATCH_QOS_BACKGROUND;
case DISPATCH_QUEUE_PRIORITY_NON_INTERACTIVE: return DISPATCH_QOS_UTILITY;
case DISPATCH_QUEUE_PRIORITY_LOW: return DISPATCH_QOS_UTILITY;
case DISPATCH_QUEUE_PRIORITY_DEFAULT: return DISPATCH_QOS_DEFAULT;
case DISPATCH_QUEUE_PRIORITY_HIGH: return DISPATCH_QOS_USER_INITIATED;
default: return _dispatch_qos_from_qos_class((qos_class_t)priority);
}
}
dispatch_get_main_queue
/*!
* @function dispatch_get_main_queue
*
* @abstract
* Returns the default queue that is bound to the main thread.
*
* @discussion
* In order to invoke blocks submitted to the main queue, the application must
* call dispatch_main(), NSApplicationMain(), or use a CFRunLoop on the main
* thread.
*
* @result
* Returns the main queue. This queue is created automatically on behalf of
* the main thread before main() is called.
*/
dispatch_queue_t
dispatch_get_main_queue(void)
{
return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
}
根據註釋,可以看到,main queue是由系統在main()方法呼叫前建立的。專門繫結到main thread上。而且,為了觸發提交到main queue上的block,和其他queue不一樣,main queue上的任務是依賴於main runloop觸發的
。
_dispatch_main_q
struct dispatch_queue_s _dispatch_main_q = {
DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
.do_targetq = &_dispatch_root_queues[
DISPATCH_ROOT_QUEUE_IDX_DEFAULT_QOS_OVERCOMMIT], // 同樣也是取root queue中的queue作為target queue
#endif
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
DISPATCH_QUEUE_ROLE_BASE_ANON,
.dq_label = "com.apple.main-thread",
.dq_atomic_flags = DQF_THREAD_BOUND | DQF_CANNOT_TRYSYNC | DQF_WIDTH(1),
.dq_serialnum = 1,
};
總結
從上面原始碼可以看出,GCD用到的queue,無論是自己建立的,或是獲取系統的main queue還是global queue,其最終都是落腳於GCD root queue中。 我們可以在程式碼中的任意位置建立queue,但最終GCD管理的,也不過這12個root queue,這種思路有點類似於命令模式
,即分散建立,集中管理
。