深入理解GCD之dispatch_queue
原文鏈接深入理解GCD之dispatch_queue
前言
上一篇我們介紹了GCD的結構體,這一篇我們著重看一下GCD中隊列的構成。隊列是我們在使用GCD中經常接觸的技術點。
關鍵點
主隊列和主線程
這兩個術語我們可以經常聽到,不知道有沒有人會把這兩個概念等同化。主隊列和主線程是有關聯(lián),但是它們是兩個不同的概念。簡單地說,主隊列是主線程上的一個串行隊列,是系統(tǒng)自動為我們創(chuàng)建的。換言之,主線程是可以執(zhí)行除主隊列之外其他隊列的任務。
隊列和線程
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; \ //鏈表的nextunsigned int do_ref_cnt; \ //引用計數unsigned int do_xref_cnt; \ //外部引用計數unsigned int do_suspend_cnt; \ //暫停標志,比如延時處理中,在任務到時后,計時器處理將會將該標志位修改,然后喚醒隊列調度struct dispatch_queue_s *do_targetq; \ //目標隊列,GCD允許我們將一個隊列放在另一個隊列里執(zhí)行任務void *do_ctxt; \ //上下文,用來存儲線程池相關數據,比如用于線程掛起和喚醒的信號量、線程池尺寸等void *do_finalizer;//第二部分:DISPATCH_QUEUE_HEADERuint32_t volatile dq_running; \ //是否運行中uint32_t dq_width; \ //最大并發(fā)數:主線程/串行中這個值為1struct dispatch_object_s *volatile dq_items_tail; \ //鏈表尾節(jié)點struct dispatch_object_s *volatile dq_items_head; \ //鏈表頭節(jié)點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種類型。
主隊列
我們在開發(fā)過程中可以使用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,所以和主隊列的生命周期應該是一樣的。
自定義隊列
我們在開發(fā)中會使用dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)創(chuàng)建一個自定義的隊列。它的源代碼如下:
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,希望編譯器進行優(yōu)化。slowpath(x)表示x的值很可能為0,希望編譯器進行優(yōu)化。
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最終都會滲透到系統(tǒng)的某一個全局隊列及其線程池。
看起來自定義隊列更像是全局隊列的一個代理。在自定義隊列創(chuàng)建的時候默認其目標隊列為_dispatch_get_root_queue(0, true)。其中0代表優(yōu)先級DISPATCH_QUEUE_PRIORITY_DEFAULT,true代表是否是overcommit。overcommit參數表示該隊列在執(zhí)行block時,無論系統(tǒng)多忙都會新開一個線程,這樣做的目的是不會造成某個線程過載。如果是自定義并發(fā)隊列的話,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_COMPATif (slowpath(dq == &_dispatch_main_q)) {return _dispatch_sync_slow(dq, work);} #endifstruct 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的執(zhí)行底層還是使用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);}//并發(fā)隊列_dispatch_sync_f2(dq, ctxt, func); } 復制代碼這里分成了三種情況:
dispatch_barrier_sync_f
如果是串行隊列壓入同步任務,那么當前任務就必須等待前面的任務執(zhí)行完成后才能執(zhí)行。源代碼就會調用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函數,等待這個隊列中的其他任務完成(信號量的方式),然后執(zhí)行這個任務。if (slowpath(dq->dq_items_tail) || slowpath(DISPATCH_OBJECT_SUSPENDED(dq))){return _dispatch_barrier_sync_f_slow(dq, ctxt, func);}//第二步:檢測隊列的dq_running狀態(tài),如果有運行,進入_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 casereturn _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);}//第四步:執(zhí)行隊列里的任務,執(zhí)行后檢測隊列有無其他任務,如果有,釋放前面的信號量(釋放信號_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就不會發(fā)生死鎖,使用queue1就會死鎖NSLog(@"22222");});}); }@end 復制代碼以_serialQueueDeadLock為例:當第一次執(zhí)行串行隊列任務的時候,跳到第四步,直接開始執(zhí)行任務,在運行第二個dispatch_sync時候,在任務里面通過執(zhí)行第一步(隊列在運行)向這個同步隊列中壓入信號量,然后等待信號量,進入死鎖。如果主隊列則會跳轉到第二步進入死鎖。
_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。這個函數的作用:執(zhí)行傳入的任務,然后根據dq_running檢測任務隊列有沒有激活,沒有激活就執(zhí)行激活函數。關于激活函數_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//第一步:并發(fā)隊列中有其他任務或者隊列被掛起,壓入信號量,等待其他線程釋放這個信號量if (slowpath(dq->dq_items_tail) || slowpath(DISPATCH_OBJECT_SUSPENDED(dq))){return _dispatch_sync_f_slow(dq, ctxt, func);}//第二步:并行隊列沒激活,激活隊列后執(zhí)行任務,最終還是調用了_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); } 復制代碼通過上面的注釋,并行隊列同步執(zhí)行是順序執(zhí)行的。這種順序執(zhí)行和操作隊列為并發(fā)隊列沒有關系。而是因為這些操作均為同步操作,所以每一個操作放入隊列后都會被等待執(zhí)行完成才會放入下一操作,造成了這種順序執(zhí)行的現象。
現在我們整理一下隊列同步執(zhí)行的流程,如下圖:
隊列的異步
說完了同步我們現在看一下異步。我們使用dispatch_async進行隊列的異步執(zhí)行。
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在執(zhí)行之前不會被銷毀,另外_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//串行隊列,執(zhí)行dispatch_barrier_async_f,其實最后還是執(zhí)行任務入隊的操作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函數進行轉發(fā)。if (dq->do_targetq) {return _dispatch_async_f2(dq, dc);}//全局隊列直接進行入隊操作_dispatch_queue_push(dq, dc); } 復制代碼從上面的源代碼中我們可以看出dispatch_async_f大致分為三種情況:
雖然上面分三種情況,但是歸根到底,它們最后執(zhí)行都是_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 //同步慢 復制代碼從上面我們可以知道串行隊列在異步執(zhí)行的時候,通過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中封裝了異步執(zhí)行任務。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區(qū)分類型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是一個宏定義,它最后會變成執(zhí)行_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_liststatic 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,然后喚醒這個隊列。因為此時隊列為空,沒有任務在執(zhí)行,處于休眠狀態(tài),所以需要喚醒_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;如果是管理隊列的的話,則執(zhí)行_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_COMPATif (dou._dq == &_dispatch_main_q) {//傳入主隊列,會進入到 _dispatch_queue_wakeup_main() 函數中_dispatch_queue_wakeup_main();} #endifreturn NULL;}_dispatch_retain(dou._do);//有目標隊列,繼續(xù)向目標隊列壓入這個隊列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
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中注冊相對應的source1kr = _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系統(tǒng)調用,通知workqueue增加應當執(zhí)行的項目。根據該通知,XNU內核基于系統(tǒng)狀態(tài)判斷是否要生成線程,如果是overcommit優(yōu)先級的隊列,workqueue則始終生成線程,之后線程執(zhí)行_dispatch_worker_thread2函數。//工作隊列,是一個用于創(chuàng)建內核線程的接口,通過它創(chuàng)建的內核線程來執(zhí)行內核其他模塊排列到隊列里的工作。不同優(yōu)先級的dispatch queue對應著對應優(yōu)先級的workqueue。GCD初始化的時候,使用pthread_workqueue_create_np創(chuàng)建pthread_workqueueif (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//通過發(fā)送一個信號量使線程保活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創(chuàng)建一個線程,并執(zhí)行_dispatch_worker_thread,_dispatch_worker_thread最終會調用到_dispatch_worker_thread2while ((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_POOLout: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/crashif (dispatch_begin_thread_4GC) {dispatch_begin_thread_4GC();}void *pool = _dispatch_begin_NSAutoReleasePool(); #endif#if DISPATCH_PERF_MONuint64_t start = _dispatch_absolute_time(); #endif//_dispatch_queue_concurrent_drain_one用來取出隊列的一個內容while ((item = fastpath(_dispatch_queue_concurrent_drain_one(dq)))) {// 用來對取出的內容進行處理(如果是任務,則執(zhí)行任務)_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
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 signalhead = 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的效率很糟糕,// 這種情況意味著在線程池中有太多的線程,這個時候應該創(chuàng)建一個pengding線程,// 然后退出該線程,內核會在負載減弱的時候創(chuàng)建一個新的線程_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();}}// 繼續(xù)調度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);}//判斷是否是groupif ((long)dc->do_vtable & DISPATCH_OBJ_GROUP_BIT) {dg = dc->dc_group;} else {dg = NULL;}//是任務封裝的 dispatch_continuation_t 結構體,直接執(zhí)行任務。//到這里我們知道了隊列執(zhí)行的時候,block被調用的時機_dispatch_client_callout(dc->dc_ctxt, dc->dc_func);if (dg) {//group需要進行調用dispatch_group_leave并釋放信號dispatch_group_leave(dg);_dispatch_release(dg);} } 復制代碼從上面的函數中可以發(fā)現,壓入隊列的不僅是任務,還有可能是隊列。如果是隊列,直接執(zhí)行了_dispatch_queue_invoke,否則執(zhí)行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 queuetq = dx_invoke(dq);} else if (slowpath(otq != dq->do_targetq)) {// An item on the queue changed the target queuetq = 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,因為任務要么被直接執(zhí)行了,要么被壓到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)) {//隊列處于空閑狀態(tài),需要喚醒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 } 復制代碼現在我們整理一下隊列異步執(zhí)行的流程,如下圖:
總結
dispatch_queue通過結構體和鏈表,被實現為FIFO(先進先出)隊列。無論串行隊列和并發(fā)隊列,都是符合FIFO的原則。兩者的主要區(qū)別是:執(zhí)行順序不同,以及開啟線程數不同。
dispatch_sync函數一般都在當前線程執(zhí)行,利用與線程綁定的信號量來實現串行。
Block并不是直接添加到隊列上,而是先構成一個dispatch_continuation結構體。結構體包含了這個Block還有一些上下文信息。隊列會將這些dispatch_continuation結構體添加隊列的鏈表中。無論這些隊列是什么類型的,最終都是和全局隊列相關的。在全局隊列執(zhí)行Block的時候,libdispatch從全局隊列中取出dispatch_continuation,調用pthread_workqueue_additem_np函數,將該全局隊列自身、符合其優(yōu)先級的workqueue信息以及dispatch_continuation結構體的回調函數傳遞給參數。pthread_workqueue_additem_np函數使用workq_kernreturn系統(tǒng)調用,通知workqueue增加應當執(zhí)行的項目。根據該同志,XNU內核基于系統(tǒng)狀態(tài)判斷是否要生成線程。如果是overcommit優(yōu)先級的全局隊列workqueue則會始終生成線程。workqueue的線程執(zhí)行pthread_workqueue函數,該函數調用libdispatch的回調函數。在該函數中執(zhí)行加入到dispatch_continuation的Block
dispatch_async分發(fā)到主隊列的任務由Runloop處理,而分發(fā)到其他隊列的任務由線程池處理。
GCD死鎖是隊列導致的而不是線程導致,原因是_dispatch_barrier_sync_f_slow函數中使用了線程對應的信號量并且調用wait方法,從而導致線程死鎖。
dispatch_barrier_async適用的場景隊列必須是用DISPATCH_QUEUE_CONCURRENT屬性創(chuàng)建的隊列,而使用全局并發(fā)隊列的時候,其表現就和dispatch_async一樣。原因:dispatch_barrier_async如果傳入的是全局隊列,在喚醒隊列時會執(zhí)行_dispatch_queue_wakeup_global函數,其執(zhí)行效果同dispatch_async一致,而如果是自定義的隊列的時候,_dispatch_continuation_pop中會執(zhí)行dispatch_queue_invoke。在while循環(huán)中依次取出任務并調用_dispatch_continuation_redirect函數,使得block并發(fā)執(zhí)行。當遇到DISPATCH_OBJ_BARRIER_BIT標記時,會修改do_suspend_cnt標志以保證后續(xù)while循環(huán)時直接goto out。barrier block的任務執(zhí)行完之后_dispatch_queue_class_invoke會將do_suspend_cnt重置回去,所以barrier block之后的任務會繼續(xù)執(zhí)行。
隊列操作和線程開啟的關系:dispatch_sync添加任務到隊列,不會創(chuàng)建新的線程。所有任務都是在當前線程中處理的。dispatch_async添加任務到隊列分為三種情況:主隊列不創(chuàng)建線程,在主線程中串行執(zhí)行;全局隊列和自定義并行隊列根據任務系統(tǒng)決定開辟線程個數;自定義串行隊列創(chuàng)建一個線程,串行進行。
總結
以上是生活随笔為你收集整理的深入理解GCD之dispatch_queue的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 磊哥评测之数据库:腾讯云MongoDB
- 下一篇: 2019测试指南-测试测试原理