前言
上一篇我們介紹了GCD的結構體,這一篇我們著重看一下GCD中佇列的構成。佇列是我們在使用GCD中經常接觸的技術點。
關鍵點
主佇列和主執行緒
這兩個術語我們可以經常聽到,不知道有沒有人會把這兩個概念等同化。主佇列和主執行緒是有關聯,但是它們是兩個不同的概念。簡單地說,主佇列是主執行緒上的一個序列佇列,是系統自動為我們建立的。換言之,主執行緒是可以執行除主佇列之外其他佇列的任務。
佇列和執行緒
Concurrent Programming: APIs and Challenges中的一張圖片可以很直觀地描述GCD與執行緒之間的關係:
一個執行緒內可能有多個佇列,這些佇列可能是序列的或者是並行的,按照同步或者非同步的方式工作。
佇列的定義
dispatch_queue_s
是佇列的結構體,可以說我們在GCD中接觸最多的結構體了。
struct dispatch_queue_vtable_s {
DISPATCH_VTABLE_HEADER(dispatch_queue_s);
};
#define DISPATCH_QUEUE_MIN_LABEL_SIZE 64
#ifdef __LP64__
#define DISPATCH_QUEUE_CACHELINE_PAD 32
#else
#define DISPATCH_QUEUE_CACHELINE_PAD 8
#endif
#define DISPATCH_QUEUE_HEADER \
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;
struct dispatch_queue_s {
DISPATCH_STRUCT_HEADER(dispatch_queue_s, dispatch_queue_vtable_s);
DISPATCH_QUEUE_HEADER;
char dq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE]; // must be last
char _dq_pad[DISPATCH_QUEUE_CACHELINE_PAD]; // for static queues only
};
複製程式碼
GCD中使用了很多的巨集,不利於我們理解程式碼,我們用對應的結構替換掉定義的巨集,如下:
struct dispatch_queue_s {
//第一部分:DISPATCH_STRUCT_HEADER(dispatch_queue_s, dispatch_queue_vtable_s)
const struct dispatch_queue_vtable_s *do_vtable; \ //dispatch_queue_s的操作函式:dispatch_queue_vtable_s型別的結構體
struct dispatch_queue_s *volatile do_next; \ //連結串列的next
unsigned int do_ref_cnt; \ //引用計數
unsigned int do_xref_cnt; \ //外部引用計數
unsigned int do_suspend_cnt; \ //暫停標誌,比如延時處理中,在任務到時後,計時器處理將會將該標誌位修改,然後喚醒佇列排程
struct dispatch_queue_s *do_targetq; \ //目標佇列,GCD允許我們將一個佇列放在另一個佇列裡執行任務
void *do_ctxt; \ //上下文,用來儲存執行緒池相關資料,比如用於執行緒掛起和喚醒的訊號量、執行緒池尺寸等
void *do_finalizer;
//第二部分:DISPATCH_QUEUE_HEADER
uint32_t volatile dq_running; \ //是否執行中
uint32_t dq_width; \ //最大併發數:主執行緒/序列中這個值為1
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; //specific佇列
//其他:
char dq_label[DISPATCH_QUEUE_MIN_LABEL_SIZE]; // must be last 說明佇列的名字要少於64個字元
char _dq_pad[DISPATCH_QUEUE_CACHELINE_PAD]; // for static queues only
};
複製程式碼
佇列的型別
佇列的型別可以分為主佇列、管理佇列、自定義佇列、全域性佇列4種型別。
主佇列
我們在開發過程中可以使用dispatch_get_main_queue
獲取主佇列,看一下它的定義:
#define dispatch_get_main_queue() (&_dispatch_main_q)
struct dispatch_queue_s _dispatch_main_q = {
#if !DISPATCH_USE_RESOLVERS
.do_vtable = &_dispatch_queue_vtable,
.do_targetq = &_dispatch_root_queues[
DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY],
#endif
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.dq_label = "com.apple.main-thread",
.dq_running = 1,
.dq_width = 1, //說明主佇列是一個序列佇列
.dq_serialnum = 1,
};
複製程式碼
它的幾個主要屬性:
1.do_vtable
const struct dispatch_queue_vtable_s _dispatch_queue_vtable = {
.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,
};
複製程式碼
2.do_targetq
主佇列的目標佇列:"com.apple.root.default-overcommit-priority"這個全域性佇列。這裡我們先提前總結一下:非全域性佇列的佇列型別(主佇列以及後面提到的管理佇列和自定義佇列),都需要壓入到全域性佇列處理,所以需要設定do_targetq
。
[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY] = {
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_ctxt = &_dispatch_root_queue_contexts[
DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY],
.dq_label = "com.apple.root.default-overcommit-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 7,
},
複製程式碼
3.do_ref_cnt和do_xref_cnt
前面提到do_ref_cnt
和do_xref_cnt
是引用計數,主佇列的這兩個值為DISPATCH_OBJECT_GLOBAL_REFCNT
。既然是引用計數,那想必是和GCD的記憶體管理有關,找到和記憶體管理相關的程式碼:
void
dispatch_retain(dispatch_object_t dou)
{
if (slowpath(dou._do->do_xref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT)) {
return; // global object
}
...
}
void
_dispatch_retain(dispatch_object_t dou)
{
if (slowpath(dou._do->do_ref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT)) {
return; // global object
}
...
}
void
dispatch_release(dispatch_object_t dou)
{
if (slowpath(dou._do->do_xref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT)) {
return;
}
...
}
void
_dispatch_release(dispatch_object_t dou)
{
if (slowpath(dou._do->do_ref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT)) {
return; // global object
}
...
}
複製程式碼
從上面幾個函式我們可以看出,主佇列的生命週期是伴隨著應用的,不會受retain和release的影響。
管理佇列
_dispatch_mgr_q
(管理佇列),是GCD的內部佇列,不對外公開。從名字上看,這個佇列應該是用來扮演管理的角色,GCD定時器就用到了管理佇列。
struct dispatch_queue_s _dispatch_mgr_q = {
.do_vtable = &_dispatch_queue_mgr_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_targetq = &_dispatch_root_queues[
DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY],
.dq_label = "com.apple.libdispatch-manager",
.dq_width = 1,
.dq_serialnum = 2,
};
複製程式碼
1.do_vtable
static const struct dispatch_queue_vtable_s _dispatch_queue_mgr_vtable = {
.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,
};
複製程式碼
2.do_targetq
管理佇列的目標佇列:"com.apple.root.high-overcommit-priority"這個全域性佇列。
[DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY] = {
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_ctxt = &_dispatch_root_queue_contexts[
DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY],
.dq_label = "com.apple.root.high-overcommit-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 9,
},
複製程式碼
3.do_ref_cnt和do_xref_cnt
管理佇列的這兩個值為DISPATCH_OBJECT_GLOBAL_REFCNT
,所以和主佇列的生命週期應該是一樣的。
自定義佇列
我們在開發中會使用dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
建立一個自定義的佇列。它的原始碼如下:
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
dispatch_queue_t dq;
size_t label_len;
if (!label) {
label = "";
}
label_len = strlen(label);
if (label_len < (DISPATCH_QUEUE_MIN_LABEL_SIZE - 1)) {
label_len = (DISPATCH_QUEUE_MIN_LABEL_SIZE - 1);
}
// XXX switch to malloc()
dq = calloc(1ul, sizeof(struct dispatch_queue_s) -
DISPATCH_QUEUE_MIN_LABEL_SIZE - DISPATCH_QUEUE_CACHELINE_PAD +
label_len + 1);
if (slowpath(!dq)) {
return dq;
}
//佇列初始化資料
_dispatch_queue_init(dq);
strcpy(dq->dq_label, label);
if (fastpath(!attr)) {
return dq;
}
if (fastpath(attr == DISPATCH_QUEUE_CONCURRENT)) {
dq->dq_width = UINT32_MAX;
dq->do_targetq = _dispatch_get_root_queue(0, false);
} else {
dispatch_debug_assert(!attr, "Invalid attribute");
}
return dq;
}
複製程式碼
1.slowpath(x)
和fastpath(x)
關於這兩個巨集的定義如下:
#define fastpath(x) ((typeof(x))__builtin_expect((long)(x), ~0l))
#define slowpath(x) ((typeof(x))__builtin_expect((long)(x), 0l))
複製程式碼
fastpath(x)
表示x的值一般不為0,希望編譯器進行優化。slowpath(x)
表示x的值很可能為0,希望編譯器進行優化。
2._dispatch_queue_init
static inline void
_dispatch_queue_init(dispatch_queue_t dq)
{
dq->do_vtable = &_dispatch_queue_vtable;
dq->do_next = DISPATCH_OBJECT_LISTLESS;
dq->do_ref_cnt = 1;
dq->do_xref_cnt = 1;
// Default target queue is overcommit!
dq->do_targetq = _dispatch_get_root_queue(0, true);
dq->dq_running = 0;
dq->dq_width = 1;
dq->dq_serialnum = dispatch_atomic_inc(&_dispatch_queue_serial_numbers) - 1;
}
複製程式碼
_dispatch_queue_init
預設設定一個佇列為序列佇列,它的目標佇列是_dispatch_get_root_queue(0, true)
。
3.do_targetq
前面對這個欄位的解釋有點簡單了,do_targetq
代表目的佇列。在Concurrent Programming: APIs and Challenges提到:
While custom queues are a powerful abstraction, all blocks you schedule on them will ultimately trickle down to one of the system’s global queues and its thread pool(s).
雖然自定義佇列是一個強大的抽象,但你在佇列上安排的所有Block最終都會滲透到系統的某一個全域性佇列及其執行緒池。
看起來自定義佇列更像是全域性佇列的一個代理。在自定義佇列建立的時候預設其目標佇列為_dispatch_get_root_queue(0, true)
。其中0
代表優先順序DISPATCH_QUEUE_PRIORITY_DEFAULT
,true
代表是否是overcommit
。overcommit
參數列示該佇列在執行block時,無論系統多忙都會新開一個執行緒,這樣做的目的是不會造成某個執行緒過載。如果是自定義併發佇列的話,do_targetq
會被設定為_dispatch_get_root_queue(0, false)
。
值得注意的是,主佇列的目標佇列也是一個全域性佇列,全域性佇列的底層就是普通的執行緒池(這個會在全域性佇列中講到)。
4.dq_serialnum
dq_serialnum
是在_dispatch_queue_serial_numbers
基礎上進行原子操作加1,即從12開始累加。1到11被保留的序列號定義如下:
// skip zero
// 1 - main_q
// 2 - mgr_q
// 3 - _unused_
// 4,5,6,7,8,9,10,11 - global queues
// we use 'xadd' on Intel, so the initial value == next assigned
複製程式碼
其中1用於主佇列,2用於管理佇列,3暫時沒有被使用,4~11是用於全域性佇列的。由於我找錯了看的原始碼是libdispatch-187.10
很老了,後面蘋果有新增了幾個佇列。
全域性佇列
上面說了很多全域性佇列,現在我們來看一下全域性佇列是如何定義的。
dispatch_queue_t
dispatch_get_global_queue(long priority, unsigned long flags)
{
if (flags & ~DISPATCH_QUEUE_OVERCOMMIT) {
return NULL;
}
return _dispatch_get_root_queue(priority,
flags & DISPATCH_QUEUE_OVERCOMMIT);
}
static inline dispatch_queue_t
_dispatch_get_root_queue(long priority, bool overcommit)
{
if (overcommit) switch (priority) {
case DISPATCH_QUEUE_PRIORITY_LOW:
return &_dispatch_root_queues[
DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY];
case DISPATCH_QUEUE_PRIORITY_DEFAULT:
return &_dispatch_root_queues[
DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY];
case DISPATCH_QUEUE_PRIORITY_HIGH:
return &_dispatch_root_queues[
DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY];
case DISPATCH_QUEUE_PRIORITY_BACKGROUND:
return &_dispatch_root_queues[
DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY];
}
switch (priority) {
case DISPATCH_QUEUE_PRIORITY_LOW:
return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY];
case DISPATCH_QUEUE_PRIORITY_DEFAULT:
return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY];
case DISPATCH_QUEUE_PRIORITY_HIGH:
return &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY];
case DISPATCH_QUEUE_PRIORITY_BACKGROUND:
return &_dispatch_root_queues[
DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY];
default:
return NULL;
}
}
DISPATCH_CACHELINE_ALIGN
struct dispatch_queue_s _dispatch_root_queues[] = {
[DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY] = {
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_ctxt = &_dispatch_root_queue_contexts[
DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY],
.dq_label = "com.apple.root.low-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 4,
},
[DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY] = {
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_ctxt = &_dispatch_root_queue_contexts[
DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY],
.dq_label = "com.apple.root.low-overcommit-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 5,
},
[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY] = {
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_ctxt = &_dispatch_root_queue_contexts[
DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY],
.dq_label = "com.apple.root.default-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 6,
},
[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY] = {
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_ctxt = &_dispatch_root_queue_contexts[
DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY],
.dq_label = "com.apple.root.default-overcommit-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 7,
},
[DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY] = {
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_ctxt = &_dispatch_root_queue_contexts[
DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY],
.dq_label = "com.apple.root.high-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 8,
},
[DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY] = {
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_ctxt = &_dispatch_root_queue_contexts[
DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY],
.dq_label = "com.apple.root.high-overcommit-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 9,
},
[DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY] = {
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_ctxt = &_dispatch_root_queue_contexts[
DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY],
.dq_label = "com.apple.root.background-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 10,
},
[DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY] = {
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_ctxt = &_dispatch_root_queue_contexts[
DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY],
.dq_label = "com.apple.root.background-overcommit-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 11,
},
};
複製程式碼
1.do_vtable
全域性佇列的do_vtable
:
static const struct dispatch_queue_vtable_s _dispatch_queue_root_vtable = {
.do_type = DISPATCH_QUEUE_GLOBAL_TYPE,
.do_kind = "global-queue",
.do_debug = dispatch_queue_debug,
.do_probe = _dispatch_queue_wakeup_global,
};
複製程式碼
2.do_ctxt
全域性佇列中有一個上下文的屬性,用來儲存執行緒池相關資料,比如用於執行緒掛起和喚醒的訊號量、執行緒池尺寸等。
它的定義如下:
static struct dispatch_root_queue_context_s _dispatch_root_queue_contexts[] = {
[DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY] = {
#if DISPATCH_ENABLE_THREAD_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_ENABLE_THREAD_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_ENABLE_THREAD_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_ENABLE_THREAD_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_ENABLE_THREAD_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_ENABLE_THREAD_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_ENABLE_THREAD_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_ENABLE_THREAD_POOL
.dgq_thread_mediator = &_dispatch_thread_mediator[
DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY],
.dgq_thread_pool_size = MAX_THREAD_COUNT,
#endif
},
};
複製程式碼
佇列的同步
dispatch_sync
dispatch_sync
的原始碼如下:
void
dispatch_sync(dispatch_queue_t dq, void (^work)(void))
{
#if DISPATCH_COCOA_COMPAT
if (slowpath(dq == &_dispatch_main_q)) {
return _dispatch_sync_slow(dq, work);
}
#endif
struct Block_basic *bb = (void *)work;
dispatch_sync_f(dq, work, (dispatch_function_t)bb->Block_invoke);
}
複製程式碼
如果這個佇列是主佇列,則呼叫_dispatch_sync_slow
,否則呼叫dispatch_sync_f
。點開_dispatch_sync_slow
返現,最終還是呼叫了dispatch_sync_f
方法。通過_dispatch_Block_copy
或者Block_basic
完成由block到function的轉換。所以block的執行底層還是使用function。
dispatch_sync_f
dispatch_sync_f
的原始碼如下:
void
dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
//序列佇列
if (fastpath(dq->dq_width == 1)) {
return dispatch_barrier_sync_f(dq, ctxt, func);
}
//全域性佇列
if (slowpath(!dq->do_targetq)) {
// the global root queues do not need strict ordering
(void)dispatch_atomic_add2o(dq, dq_running, 2);
return _dispatch_sync_f_invoke(dq, ctxt, func);
}
//併發佇列
_dispatch_sync_f2(dq, ctxt, func);
}
複製程式碼
這裡分成了三種情況:
- 如果是序列佇列,執行
dispatch_barrier_sync_f
; - 如果是全域性佇列,執行
_dispatch_sync_f_invoke
; - 如果是並行佇列,執行
_dispatch_sync_f2
。
dispatch_barrier_sync_f
如果是序列佇列壓入同步任務,那麼當前任務就必須等待前面的任務執行完成後才能執行。原始碼就會呼叫dispatch_barrier_sync_f
函式完成上面的效果。
void
dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func)
{
// 1) ensure that this thread hasn't enqueued anything ahead of this call
// 2) the queue is not suspended
//第一步:如果序列佇列中存在其他任務或者佇列被掛起,則直接進入_dispatch_sync_f_slow函式,等待這個佇列中的其他任務完成(訊號量的方式),然後執行這個任務。
if (slowpath(dq->dq_items_tail) || slowpath(DISPATCH_OBJECT_SUSPENDED(dq))){
return _dispatch_barrier_sync_f_slow(dq, ctxt, func);
}
//第二步:檢測佇列的dq_running狀態,如果有執行,進入_dispatch_barrier_sync_f_slow,等待啟用。
if (slowpath(!dispatch_atomic_cmpxchg2o(dq, dq_running, 0, 1))) {
// global queues and main queue bound to main thread always falls into
// the slow case
return _dispatch_barrier_sync_f_slow(dq, ctxt, func);
}
//第三步:有多重佇列,尋找真正的目標佇列,其實還是回到了dispatch_sync_f方法
if (slowpath(dq->do_targetq->do_targetq)) {
return _dispatch_barrier_sync_f_recurse(dq, ctxt, func);
}
//第四步:執行佇列裡的任務,執行後檢測佇列有無其他任務,如果有,釋放前面的訊號量(釋放訊號_dispatch_barrier_sync_f2函式中)。
_dispatch_barrier_sync_f_invoke(dq, ctxt, func);
}
複製程式碼
看了上面的程式碼註釋後,我們來想一下同步序列佇列死鎖問題。死鎖是怎麼產生的?先看下示例程式碼:
#import "DeadLock.h"
@implementation DeadLock
- (instancetype)init {
if (self = [super init]) {
// [self _mianQueueDeadLock];
[self _serialQueueDeadLock];
}
return self;
}
#pragma mark - Private
- (void)_mianQueueDeadLock {
dispatch_sync(dispatch_get_main_queue(), ^(void){
NSLog(@"這裡死鎖了");
});
}
- (void)_serialQueueDeadLock {
dispatch_queue_t queue1 = dispatch_queue_create("1serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("2serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue1, ^{
NSLog(@"11111");
dispatch_sync(queue1, ^{//如果使用queue2就不會發生死鎖,使用queue1就會死鎖
NSLog(@"22222");
});
});
}
@end
複製程式碼
以_serialQueueDeadLock
為例:當第一次執行序列佇列任務的時候,跳到第四步,直接開始執行任務,在執行第二個dispatch_sync
時候,在任務裡面通過執行第一步(佇列在執行)向這個同步佇列中壓入訊號量,然後等待訊號量,進入死鎖。如果主佇列則會跳轉到第二步進入死鎖。
_dispatch_sync_f_invoke
static void
_dispatch_sync_f_invoke(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func)
{
_dispatch_function_invoke(dq, ctxt, func);
if (slowpath(dispatch_atomic_sub2o(dq, dq_running, 2) == 0)) {
_dispatch_wakeup(dq);
}
}
複製程式碼
如果當前佇列是全域性佇列的話,就會呼叫_dispatch_sync_f_invoke
。這個函式的作用:執行傳入的任務,然後根據dq_running檢測任務佇列有沒有啟用,沒有啟用就執行啟用函式。關於啟用函式_dispatch_wakeup(dq)
放在佇列的非同步中講解。
_dispatch_sync_f2
_dispatch_sync_f2(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
// 1) ensure that this thread hasn't enqueued anything ahead of this call
// 2) the queue is not suspended
//第一步:併發佇列中有其他任務或者佇列被掛起,壓入訊號量,等待其他執行緒釋放這個訊號量
if (slowpath(dq->dq_items_tail) || slowpath(DISPATCH_OBJECT_SUSPENDED(dq))){
return _dispatch_sync_f_slow(dq, ctxt, func);
}
//第二步:並行佇列沒啟用,啟用佇列後執行任務,最終還是呼叫了_dispatch_sync_f_slow函式,只是多了一個啟用函式
if (slowpath(dispatch_atomic_add2o(dq, dq_running, 2) & 1)) {
return _dispatch_sync_f_slow2(dq, ctxt, func);
}
//第三步:佇列有多重佇列,尋找真正的目標佇列
if (slowpath(dq->do_targetq->do_targetq)) {
return _dispatch_sync_f_recurse(dq, ctxt, func);
}
//第四步:並行佇列沒有其他任務,呼叫並啟用這個佇列
_dispatch_sync_f_invoke(dq, ctxt, func);
}
複製程式碼
通過上面的註釋,並行佇列同步執行是順序執行的。這種順序執行和操作佇列為併發佇列沒有關係。而是因為這些操作均為同步操作,所以每一個操作放入佇列後都會被等待執行完成才會放入下一操作,造成了這種順序執行的現象。
現在我們整理一下佇列同步執行的流程,如下圖:
佇列的非同步
說完了同步我們現在看一下非同步。我們使用dispatch_async
進行佇列的非同步執行。
dispatch_async
dispatch_async
的原始碼如下:
void
dispatch_async(dispatch_queue_t dq, void (^work)(void))
{
dispatch_async_f(dq, _dispatch_Block_copy(work),
_dispatch_call_block_and_release);
}
複製程式碼
dispatch_async
主要將block從棧copy到堆上,或者增加引用計數,保證block在執行之前不會被銷燬,另外_dispatch_call_block_and_release
用於銷燬block。然後呼叫dispatch_async_f
。
dispatch_async_f
void
dispatch_async_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
dispatch_continuation_t dc;
// No fastpath/slowpath hint because we simply don't know
//序列佇列,執行dispatch_barrier_async_f,其實最後還是執行任務入隊的操作
if (dq->dq_width == 1) {
return dispatch_barrier_async_f(dq, ctxt, func);
}
//從執行緒私有資料中獲取一個dispatch_continuation_t的結構體
dc = fastpath(_dispatch_continuation_alloc_cacheonly());
if (!dc) {
return _dispatch_async_f_slow(dq, ctxt, func);
}
dc->do_vtable = (void *)DISPATCH_OBJ_ASYNC_BIT;
dc->dc_func = func;
dc->dc_ctxt = ctxt;
// No fastpath/slowpath hint because we simply don't know
//有目標佇列,呼叫_dispatch_async_f2函式進行轉發。
if (dq->do_targetq) {
return _dispatch_async_f2(dq, dc);
}
//全域性佇列直接進行入隊操作
_dispatch_queue_push(dq, dc);
}
複製程式碼
從上面的原始碼中我們可以看出dispatch_async_f
大致分為三種情況:
- 如果是序列佇列,呼叫
dispatch_barrier_async_f
; - 其他佇列且有目標佇列,呼叫
_dispatch_async_f2
; - 如果是全域性佇列的話,直接進行入隊操作。
雖然上面分三種情況,但是歸根到底,它們最後執行都是_dispatch_queue_push
來進行入隊的操作。
這裡有一點需要注意下:就是dispatch_continuation_t
中do_vtable
的賦值情況。
//序列佇列,barrier
dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
//not barrier
dc->do_vtable = (void *)DISPATCH_OBJ_ASYNC_BIT;
複製程式碼
在libdispatch
全部識別符號有四種:
#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 //同步慢
複製程式碼
從上面我們可以知道序列佇列在非同步執行的時候,通過DISPATCH_OBJ_BARRIER_BIT
這個識別符號實現阻塞等待的。
接著我們分析下dispatch_barrier_async_f
和_dispatch_async_f2
這兩個函式。
dispatch_barrier_async_f
dispatch_barrier_async_f
的原始碼如下:
void
dispatch_barrier_async_f(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func)
{
dispatch_continuation_t dc;
//從執行緒私有資料中獲取一個dispatch_continuation_t的結構體,dispatch_continuation_t中封裝了非同步執行任務。
dc = fastpath(_dispatch_continuation_alloc_cacheonly());
if (!dc) {
//return _dispatch_barrier_async_f_slow(dq, ctxt, func);
//以下是_dispatch_barrier_async_f_slow的具體實現
//如果沒有則從堆上獲取一個dispatch_continuation_t的結構體
dispatch_continuation_t dc = _dispatch_continuation_alloc_from_heap();
//通過do_vtable區分型別
dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
//將_dispatch_call_block_and_release作為func方法
dc->dc_func = func;
//將傳入的block作為上下文
dc->dc_ctxt = ctxt;
//入隊操作
_dispatch_queue_push(dq, dc);
}
dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
dc->dc_func = func;
dc->dc_ctxt = ctxt;
_dispatch_queue_push(dq, dc);
}
複製程式碼
_dispatch_queue_push
_dispatch_queue_push
是一個巨集定義,它最後會變成執行_dispatch_queue_push_list
函式。
#define _dispatch_queue_push(x, y) _dispatch_queue_push_list((x), (y), (y))
#define _dispatch_queue_push_list _dispatch_trace_queue_push_list
static inline void
_dispatch_trace_queue_push_list(dispatch_queue_t dq, dispatch_object_t _head,
dispatch_object_t _tail)
{
if (slowpath(DISPATCH_QUEUE_PUSH_ENABLED())) {
struct dispatch_object_s *dou = _head._do;
do {
//主要是對dispatch_continuation_s結構體的處理,確保後面的使用。
_dispatch_trace_continuation(dq, dou, DISPATCH_QUEUE_PUSH);
} while (dou != _tail._do && (dou = dou->do_next));
}
_dispatch_queue_push_list(dq, _head, _tail);
}
static inline void
_dispatch_queue_push_list(dispatch_queue_t dq, dispatch_object_t _head,
dispatch_object_t _tail)
{
struct dispatch_object_s *prev, *head = _head._do, *tail = _tail._do;
tail->do_next = NULL;
dispatch_atomic_store_barrier();
//dispatch_atomic_xchg2o實質是呼叫((typeof(*(p)))__sync_swap((p), (n))),它的定義是將p設為n並返回p操作之前的值。
//dispatch_atomic_xchg2o(dq, dq_items_tail, tail)相當於dq->dq_items_tail = tail,重新設定了佇列的尾指標
prev = fastpath(dispatch_atomic_xchg2o(dq, dq_items_tail, tail));
if (prev) {
// if we crash here with a value less than 0x1000, then we are at a
// known bug in client code for example, see _dispatch_queue_dispose
// or _dispatch_atfork_child
//prev是原先的隊尾,如果佇列中有其他的元素,就將壓入的物件加在佇列的尾部。
prev->do_next = head;
} else {
//如果佇列為空
_dispatch_queue_push_list_slow(dq, head);
}
}
複製程式碼
_dispatch_queue_push_list_slow
如果佇列為空,呼叫_dispatch_queue_push_list_slow
方法。
_dispatch_queue_push_list_slow(dispatch_queue_t dq,
struct dispatch_object_s *obj)
{
//dq->dq_items_head設定為dc,然後喚醒這個佇列。因為此時佇列為空,沒有任務在執行,處於休眠狀態,所以需要喚醒
_dispatch_retain(dq);
dq->dq_items_head = obj;
_dispatch_wakeup(dq);
_dispatch_release(dq);
}
複製程式碼
_dispatch_wakeup
無論是同步還是非同步中都呼叫了_dispatch_wakeup
,這個函式的作用就是喚醒當前佇列。
_dispatch_wakeup
的原始碼:
dispatch_queue_t
_dispatch_wakeup(dispatch_object_t dou)
{
dispatch_queue_t tq;
if (slowpath(DISPATCH_OBJECT_SUSPENDED(dou._do))) {
return NULL;
}
//這裡比較隱晦,這裡其實是走全域性佇列的喚醒邏輯呼叫_dispatch_queue_wakeup_global,如果喚醒失敗且對尾指標為空,返回NULL;如果是管理佇列的的話,則執行_dispatch_mgr_wakeup函式
if (!dx_probe(dou._do) && !dou._dq->dq_items_tail) {
return NULL;
}
// _dispatch_source_invoke() relies on this testing the whole suspend count
// word, not just the lock bit. In other words, no point taking the lock
// if the source is suspended or canceled.
if (!dispatch_atomic_cmpxchg2o(dou._do, do_suspend_cnt, 0,
DISPATCH_OBJECT_SUSPEND_LOCK)) {
#if DISPATCH_COCOA_COMPAT
if (dou._dq == &_dispatch_main_q) {
//傳入主佇列,會進入到 _dispatch_queue_wakeup_main() 函式中
_dispatch_queue_wakeup_main();
}
#endif
return NULL;
}
_dispatch_retain(dou._do);
//有目標佇列,繼續向目標佇列壓入這個佇列
tq = dou._do->do_targetq;
_dispatch_queue_push(tq, dou._do);
return tq; // libdispatch does not need this, but the Instrument DTrace
// probe does
}
複製程式碼
從上面的程式碼可以看出_dispatch_wakeup
分為四種情況:
- 主佇列呼叫
_dispatch_queue_wakeup_main()
。 - 全域性佇列呼叫
_dispatch_queue_wakeup_global
。 - 其他佇列像目標佇列壓入這個佇列,繼續做入隊操作。
- 管理佇列呼叫
_dispatch_mgr_wakeup
,這裡主要是為了dispatch_source而服務的。
_dispatch_queue_wakeup_main
void
_dispatch_queue_wakeup_main(void)
{
kern_return_t kr;
dispatch_once_f(&_dispatch_main_q_port_pred, NULL,
_dispatch_main_q_port_init);
//喚醒主執行緒,這裡已經點不進去了,關於主線的喚醒主要靠mach_port和在runloop中註冊相對應的source1
kr = _dispatch_send_wakeup_main_thread(main_q_port, 0);
switch (kr) {
case MACH_SEND_TIMEOUT:
case MACH_SEND_TIMED_OUT:
case MACH_SEND_INVALID_DEST:
break;
default:
(void)dispatch_assume_zero(kr);
break;
}
_dispatch_safe_fork = false;
}
複製程式碼
_dispatch_queue_wakeup_global
上面提到dx_probe(dou._do)
這裡走的是全域性佇列的喚醒。前面提到全域性佇列的do_vtable
:
static const struct dispatch_queue_vtable_s _dispatch_queue_root_vtable = {
.do_type = DISPATCH_QUEUE_GLOBAL_TYPE,
.do_kind = "global-queue",
.do_debug = dispatch_queue_debug,
.do_probe = _dispatch_queue_wakeup_global,
};
複製程式碼
_dispatch_queue_wakeup_global
的原始碼:
static bool
_dispatch_queue_wakeup_global(dispatch_queue_t dq)
{
static dispatch_once_t pred;
struct dispatch_root_queue_context_s *qc = dq->do_ctxt;
int r;
if (!dq->dq_items_tail) {
return false;
}
_dispatch_safe_fork = false;
dispatch_debug_queue(dq, __PRETTY_FUNCTION__);
dispatch_once_f(&pred, NULL, _dispatch_root_queues_init);
#if HAVE_PTHREAD_WORKQUEUES
#if DISPATCH_ENABLE_THREAD_POOL
//佇列上下文的dgq_kworkqueue存在,則呼叫pthread_workqueue_additem_np函式,該函式使用workq_kernreturn系統呼叫,通知workqueue增加應當執行的專案。根據該通知,XNU核心基於系統狀態判斷是否要生成執行緒,如果是overcommit優先順序的佇列,workqueue則始終生成執行緒,之後執行緒執行_dispatch_worker_thread2函式。
//工作佇列,是一個用於建立核心執行緒的介面,通過它建立的核心執行緒來執行核心其他模組排列到佇列裡的工作。不同優先順序的dispatch queue對應著對應優先順序的workqueue。GCD初始化的時候,使用pthread_workqueue_create_np建立pthread_workqueue
if (qc->dgq_kworkqueue)
#endif
{
if (dispatch_atomic_cmpxchg2o(qc, dgq_pending, 0, 1)) {
pthread_workitem_handle_t wh;
unsigned int gen_cnt;
_dispatch_debug("requesting new worker thread");
r = pthread_workqueue_additem_np(qc->dgq_kworkqueue,
_dispatch_worker_thread2, dq, &wh, &gen_cnt);
(void)dispatch_assume_zero(r);
} else {
_dispatch_debug("work thread request still pending on global "
"queue: %p", dq);
}
goto out;
}
#endif // HAVE_PTHREAD_WORKQUEUES
#if DISPATCH_ENABLE_THREAD_POOL
//通過傳送一個訊號量使執行緒保活
if (dispatch_semaphore_signal(qc->dgq_thread_mediator)) {
goto out;
}
pthread_t pthr;
int t_count;
do {
t_count = qc->dgq_thread_pool_size;
if (!t_count) {
_dispatch_debug("The thread pool is full: %p", dq);
goto out;
}
} while (!dispatch_atomic_cmpxchg2o(qc, dgq_thread_pool_size, t_count,
t_count - 1));//如果執行緒池可用則減1
//這裡說明執行緒池不夠用了,使用pthread建立一個執行緒,並執行_dispatch_worker_thread,_dispatch_worker_thread最終會呼叫到_dispatch_worker_thread2
while ((r = pthread_create(&pthr, NULL, _dispatch_worker_thread, dq))) {
if (r != EAGAIN) {
(void)dispatch_assume_zero(r);
}
sleep(1);
}
//保證pthr能被自動回收掉
r = pthread_detach(pthr);
(void)dispatch_assume_zero(r);
#endif // DISPATCH_ENABLE_THREAD_POOL
out:
return false;
}
複製程式碼
_dispatch_worker_thread2
_dispatch_worker_thread2
的程式碼如下:
_dispatch_worker_thread2(void *context)
{
struct dispatch_object_s *item;
dispatch_queue_t dq = context;
struct dispatch_root_queue_context_s *qc = dq->do_ctxt;
if (_dispatch_thread_getspecific(dispatch_queue_key)) {
DISPATCH_CRASH("Premature thread recycling");
}
//把dq設定為剛啟動的這個執行緒的TSD
_dispatch_thread_setspecific(dispatch_queue_key, dq);
qc->dgq_pending = 0;
#if DISPATCH_COCOA_COMPAT
(void)dispatch_atomic_inc(&_dispatch_worker_threads);
// ensure that high-level memory management techniques do not leak/crash
if (dispatch_begin_thread_4GC) {
dispatch_begin_thread_4GC();
}
void *pool = _dispatch_begin_NSAutoReleasePool();
#endif
#if DISPATCH_PERF_MON
uint64_t start = _dispatch_absolute_time();
#endif
//_dispatch_queue_concurrent_drain_one用來取出佇列的一個內容
while ((item = fastpath(_dispatch_queue_concurrent_drain_one(dq)))) {
// 用來對取出的內容進行處理(如果是任務,則執行任務)
_dispatch_continuation_pop(item);
}
#if DISPATCH_PERF_MON
_dispatch_queue_merge_stats(start);
#endif
#if DISPATCH_COCOA_COMPAT
_dispatch_end_NSAutoReleasePool(pool);
dispatch_end_thread_4GC();
if (!dispatch_atomic_dec(&_dispatch_worker_threads) &&
dispatch_no_worker_threads_4GC) {
dispatch_no_worker_threads_4GC();
}
#endif
_dispatch_thread_setspecific(dispatch_queue_key, NULL);
_dispatch_force_cache_cleanup();
}
複製程式碼
這裡有兩個比較重要的方法:
_dispatch_queue_concurrent_drain_one
這個方法用來取出佇列中的一個內容。_dispatch_continuation_pop
這個方法用來處理取出的內容
_dispatch_queue_concurrent_drain_one
struct dispatch_object_s *
_dispatch_queue_concurrent_drain_one(dispatch_queue_t dq)
{
struct dispatch_object_s *head, *next, *const mediator = (void *)~0ul;
// The mediator value acts both as a "lock" and a signal
head = dispatch_atomic_xchg(&dq->dq_items_head, mediator);
if (slowpath(head == NULL)) {
//佇列是空的
dispatch_atomic_cmpxchg(&dq->dq_items_head, mediator, NULL);
_dispatch_debug("no work on global work queue");
return NULL;
}
if (slowpath(head == mediator)) {
// 該執行緒在現執行緒競爭中失去了對佇列的擁有權,這意味著libdispatch的效率很糟糕,
// 這種情況意味著線上程池中有太多的執行緒,這個時候應該建立一個pengding執行緒,
// 然後退出該執行緒,核心會在負載減弱的時候建立一個新的執行緒
_dispatch_queue_wakeup_global(dq);
return NULL;
}
// 在返回之前將head指標的do_next儲存下來,如果next為NULL,這意味著item是最後一個
next = fastpath(head->do_next);
if (slowpath(!next)) {
dq->dq_items_head = NULL;
if (dispatch_atomic_cmpxchg(&dq->dq_items_tail, head, NULL)) {
// head 和 tail頭尾指標均為空
goto out;
}
// 此時一定有item,該執行緒不會等待太久.
while (!(next = head->do_next)) {
_dispatch_hardware_pause();
}
}
// 繼續排程
dq->dq_items_head = next;
_dispatch_queue_wakeup_global(dq);
out:
// 返回佇列的頭指標
return head;
}
複製程式碼
_dispatch_continuation_pop
static inline void
_dispatch_continuation_pop(dispatch_object_t dou)
{
dispatch_continuation_t dc = dou._dc;
dispatch_group_t dg;
_dispatch_trace_continuation_pop(_dispatch_queue_get_current(), dou);
//檢測是不是佇列,如果是,就進入_dispatch_queue_invoke 處理佇列
if (DISPATCH_OBJ_IS_VTABLE(dou._do)) {
return _dispatch_queue_invoke(dou._dq);
}
// Add the item back to the cache before calling the function. This
// allows the 'hot' continuation to be used for a quick callback.
//
// The ccache version is per-thread.
// Therefore, the object has not been reused yet.
// This generates better assembly.
if ((long)dc->do_vtable & DISPATCH_OBJ_ASYNC_BIT) {
_dispatch_continuation_free(dc);
}
//判斷是否是group
if ((long)dc->do_vtable & DISPATCH_OBJ_GROUP_BIT) {
dg = dc->dc_group;
} else {
dg = NULL;
}
//是任務封裝的 dispatch_continuation_t 結構體,直接執行任務。
//到這裡我們知道了佇列執行的時候,block被呼叫的時機
_dispatch_client_callout(dc->dc_ctxt, dc->dc_func);
if (dg) {
//group需要進行呼叫dispatch_group_leave並釋放訊號
dispatch_group_leave(dg);
_dispatch_release(dg);
}
}
複製程式碼
從上面的函式中可以發現,壓入佇列的不僅是任務,還有可能是佇列。如果是佇列,直接執行了_dispatch_queue_invoke
,否則執行dc->dc_func(dc->dc_ctxt)
。
_dispatch_queue_invoke
void
_dispatch_queue_invoke(dispatch_queue_t dq)
{
if (!slowpath(DISPATCH_OBJECT_SUSPENDED(dq)) &&
fastpath(dispatch_atomic_cmpxchg2o(dq, dq_running, 0, 1))) {
dispatch_atomic_acquire_barrier();
dispatch_queue_t otq = dq->do_targetq, tq = NULL;
_dispatch_queue_drain(dq);
if (dq->do_vtable->do_invoke) {
// Assume that object invoke checks it is executing on correct queue
tq = dx_invoke(dq);
} else if (slowpath(otq != dq->do_targetq)) {
// An item on the queue changed the target queue
tq = dq->do_targetq;
}
// We do not need to check the result.
// When the suspend-count lock is dropped, then the check will happen.
dispatch_atomic_release_barrier();
//dq_running減1,因為任務要麼被直接執行了,要麼被壓到target佇列了
(void)dispatch_atomic_dec2o(dq, dq_running);
if (tq) {
return _dispatch_queue_push(tq, dq);
}
}
dq->do_next = DISPATCH_OBJECT_LISTLESS;
if (!dispatch_atomic_sub2o(dq, do_suspend_cnt,
DISPATCH_OBJECT_SUSPEND_LOCK)) {
//佇列處於空閒狀態,需要喚醒
if (dq->dq_running == 0) {
_dispatch_wakeup(dq); // verify that the queue is idle
}
}
//釋放佇列
_dispatch_release(dq); // added when the queue is put on the list
}
複製程式碼
現在我們整理一下佇列非同步執行的流程,如下圖:
總結
-
dispatch_queue
通過結構體和連結串列,被實現為FIFO
(先進先出)佇列。無論序列佇列和併發佇列,都是符合FIFO
的原則。兩者的主要區別是:執行順序不同,以及開啟執行緒數不同。 -
dispatch_sync
函式一般都在當前執行緒執行,利用與執行緒繫結的訊號量來實現序列。 -
Block並不是直接新增到佇列上,而是先構成一個
dispatch_continuation
結構體。結構體包含了這個Block還有一些上下文資訊。佇列會將這些dispatch_continuation
結構體新增佇列的連結串列中。無論這些佇列是什麼型別的,最終都是和全域性佇列相關的。在全域性佇列執行Block的時候,libdispatch從全域性佇列中取出dispatch_continuation
,呼叫pthread_workqueue_additem_np
函式,將該全域性佇列自身、符合其優先順序的workqueue資訊以及dispatch_continuation
結構體的回撥函式傳遞給引數。pthread_workqueue_additem_np
函式使用workq_kernreturn
系統呼叫,通知workqueue增加應當執行的專案。根據該同志,XNU核心基於系統狀態判斷是否要生成執行緒。如果是overcommit優先順序的全域性佇列workqueue則會始終生成執行緒。workqueue的執行緒執行pthread_workqueue
函式,該函式呼叫libdispatch的回撥函式。在該函式中執行加入到dispatch_continuation
的Block -
dispatch_async
分發到主佇列的任務由Runloop
處理,而分發到其他佇列的任務由執行緒池處理。 -
GCD死鎖是佇列導致的而不是執行緒導致,原因是
_dispatch_barrier_sync_f_slow
函式中使用了執行緒對應的訊號量並且呼叫wait方法,從而導致執行緒死鎖。 -
dispatch_barrier_async
適用的場景佇列必須是用DISPATCH_QUEUE_CONCURRENT
屬性建立的佇列,而使用全域性併發佇列的時候,其表現就和dispatch_async
一樣。原因:dispatch_barrier_async
如果傳入的是全域性佇列,在喚醒佇列時會執行_dispatch_queue_wakeup_global
函式,其執行效果同dispatch_async
一致,而如果是自定義的佇列的時候,_dispatch_continuation_pop
中會執行dispatch_queue_invoke
。在while迴圈中依次取出任務並呼叫_dispatch_continuation_redirect
函式,使得block併發執行。當遇到DISPATCH_OBJ_BARRIER_BIT
標記時,會修改do_suspend_cnt
標誌以保證後續while迴圈時直接goto out。barrier block的任務執行完之後_dispatch_queue_class_invoke
會將do_suspend_cnt
重置回去,所以barrier block之後的任務會繼續執行。 -
佇列操作和執行緒開啟的關係:
dispatch_sync
新增任務到佇列,不會建立新的執行緒。所有任務都是在當前執行緒中處理的。dispatch_async
新增任務到佇列分為三種情況:主佇列不建立執行緒,在主執行緒中序列執行;全域性佇列和自定義並行佇列根據任務系統決定開闢執行緒個數;自定義序列佇列建立一個執行緒,序列進行。