深入理解GCD之dispatch_queue

NeroXie發表於2019-02-27

原文連結深入理解GCD之dispatch_queue

前言

上一篇我們介紹了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_cntdo_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_DEFAULTtrue代表是否是overcommitovercommit參數列示該佇列在執行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);
}
複製程式碼

這裡分成了三種情況:

  1. 如果是序列佇列,執行dispatch_barrier_sync_f
  2. 如果是全域性佇列,執行_dispatch_sync_f_invoke
  3. 如果是並行佇列,執行_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_sync

佇列的非同步

說完了同步我們現在看一下非同步。我們使用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大致分為三種情況:

  1. 如果是序列佇列,呼叫dispatch_barrier_async_f
  2. 其他佇列且有目標佇列,呼叫_dispatch_async_f2
  3. 如果是全域性佇列的話,直接進行入隊操作。

雖然上面分三種情況,但是歸根到底,它們最後執行都是_dispatch_queue_push來進行入隊的操作。

這裡有一點需要注意下:就是dispatch_continuation_tdo_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分為四種情況:

  1. 主佇列呼叫_dispatch_queue_wakeup_main()
  2. 全域性佇列呼叫_dispatch_queue_wakeup_global
  3. 其他佇列像目標佇列壓入這個佇列,繼續做入隊操作。
  4. 管理佇列呼叫_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();

}
複製程式碼

這裡有兩個比較重要的方法:

  1. _dispatch_queue_concurrent_drain_one這個方法用來取出佇列中的一個內容。
  2. _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_async

總結

  1. dispatch_queue通過結構體和連結串列,被實現為FIFO(先進先出)佇列。無論序列佇列和併發佇列,都是符合FIFO的原則。兩者的主要區別是:執行順序不同,以及開啟執行緒數不同。

  2. dispatch_sync函式一般都在當前執行緒執行,利用與執行緒繫結的訊號量來實現序列。

  3. 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

  4. dispatch_async分發到主佇列的任務由Runloop處理,而分發到其他佇列的任務由執行緒池處理。

  5. GCD死鎖是佇列導致的而不是執行緒導致,原因是_dispatch_barrier_sync_f_slow函式中使用了執行緒對應的訊號量並且呼叫wait方法,從而導致執行緒死鎖。

  6. 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之後的任務會繼續執行。

  7. 佇列操作和執行緒開啟的關係:dispatch_sync新增任務到佇列,不會建立新的執行緒。所有任務都是在當前執行緒中處理的。dispatch_async新增任務到佇列分為三種情況:主佇列不建立執行緒,在主執行緒中序列執行;全域性佇列和自定義並行佇列根據任務系統決定開闢執行緒個數;自定義序列佇列建立一個執行緒,序列進行。

相關文章