一轰异、前言
在iOS開發(fā)過程中,我們知道多線程
技術(shù)是使用最多的情況暑始,能快速的執(zhí)行多個調(diào)度任務(wù)的執(zhí)行搭独。而在多線程
開發(fā)過程當(dāng)中,多線程技術(shù)有好幾種廊镜,其中包括pthread
牙肝,NSThread
,NSOperation
和GCD
,而GCD是整個iOS開發(fā)過程中使用最多的也是最安全的一種技術(shù)嗤朴,因為GCD是基于C/C++函數(shù)的封裝實現(xiàn)配椭,因此線程比較安全,在多線程開發(fā)過程中為我們開發(fā)者省去了基于考慮線程安全的事情雹姊,專注開發(fā)颂郎。是多線程開發(fā)過程中的首選。
然而GCD
中
*1 是如何來分配多線程的調(diào)度任務(wù)的容为?
*2 結(jié)構(gòu)又有哪些乓序?
*3任務(wù)的調(diào)度過程是如何調(diào)度的?
帶著這一系列的問題我們開始探索GCD
的函數(shù)與隊列的搭配使用情況坎背。
二替劈、函數(shù)
在我們測試的Demo中我們執(zhí)行一個相關(guān)的GCD同步函數(shù)dispatch_async
,同時向編譯器下一個符號斷點 簡單的打印一個任務(wù)
dispatch_async(conque, ^{
NSLog(@"12334");
});
我們會看到當(dāng)前的斷點會定位到系統(tǒng)的libdispatch.dylib dispatch_async:
通過以上我們就知道GCD的源碼在libdispatch.dylib
得滤,帶著找到開源的庫陨献。懷著一個好奇的心去看看具體的GCD相關(guān)函數(shù)是如何實現(xiàn),底層的調(diào)用機制又是怎么樣的懂更,接下來讓我們進入函數(shù)的探索環(huán)節(jié)吧
2.1 dispatch_sync(同步函數(shù))
我們進去到libdispatch.dylib
眨业,進行全局的搜索dispatch_sync
會找到相應(yīng)的函數(shù)定義
void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
uintptr_t dc_flags = DC_FLAG_BLOCK;
if (unlikely(_dispatch_block_has_private_data(work))) {
return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
}
_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}
我們能看到底層是調(diào)用了一個_dispatch_sync_f
函數(shù)來實現(xiàn)同步函數(shù)的實現(xiàn)的。我們再次進入看看當(dāng)前函數(shù)是如何實現(xiàn)的
static void
_dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func,
uintptr_t dc_flags)
{
_dispatch_sync_f_inline(dq, ctxt, func, dc_flags);
}
我們從源碼就能看到當(dāng)前_dispatch_sync_f
是通過封裝一個叫_dispatch_sync_f_inline
內(nèi)聯(lián)函數(shù)從而達(dá)到相關(guān)的同步函數(shù)
_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func, uintptr_t dc_flags)
{
if (likely(dq->dq_width == 1)) {
return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
}
_dispatch_introspection_sync_begin(dl);
_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));
}
再次順著相應(yīng)的開源的代碼進入到同步函數(shù)的執(zhí)行以及完成函數(shù)_dispatch_sync_invoke_and_complete
_dispatch_sync_invoke_and_complete(dispatch_lane_t dq, void *ctxt,
dispatch_function_t func DISPATCH_TRACE_ARG(void *dc))
{
_dispatch_sync_function_invoke_inline(dq, ctxt, func);
_dispatch_trace_item_complete(dc);
_dispatch_lane_non_barrier_complete(dq, 0);
}
再次進入到_dispatch_sync_function_invoke_inline
的我們能看到相應(yīng)的函數(shù)調(diào)用過程
_dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt,
dispatch_function_t func)
{
dispatch_thread_frame_s dtf;
_dispatch_thread_frame_push(&dtf, dq);
_dispatch_client_callout(ctxt, func);
_dispatch_perfmon_workitem_inc();
_dispatch_thread_frame_pop(&dtf);
}
最終到_dispatch_client_callout
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
@try {
return f(ctxt);
}
@catch (...) {
objc_terminate();
}
}
執(zhí)行步驟如下:
- 1 先給任務(wù)分配一個任務(wù)棧
- 2把當(dāng)前調(diào)度任務(wù)進行入棧沮协。讓當(dāng)前線程準(zhǔn)備開始調(diào)度相關(guān)的任務(wù)
- 3 線程調(diào)度當(dāng)前的任務(wù)
- 4 執(zhí)行完成后把當(dāng)前任務(wù)彈棧龄捡。進行相應(yīng)的釋放操作,
總結(jié):同步函數(shù)的執(zhí)行流程是dispatch_async -> _dispatch_sync_f -> _dispatch_sync_f_inline -> _dispatch_sync_invoke_and_complete -> _dispatch_sync_function_invoke_inline -> _dispatch_client_callout -> f(ctxt);
2.2dispatch_async(異步函數(shù))
再次全局搜索2dispatch_async
進入到相關(guān)的異步函數(shù)的定義如下:
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME;
dispatch_qos_t qos;
// 任務(wù)包裝器 - 接受 - 保存 - 函數(shù)式
// 保存 block
qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
我們通過代碼知道慷暂,異步函數(shù)是通過一個包裝器進行相應(yīng)的任務(wù)包裝聘殖,然后進行相應(yīng)的函數(shù)執(zhí)行:進入到_dispatch_continuation_async
一探究竟;源碼如下
static inline void
_dispatch_continuation_async(dispatch_queue_class_t dqu,
dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags)
{
#if DISPATCH_INTROSPECTION
if (!(dc_flags & DC_FLAG_NO_INTROSPECTION)) {
_dispatch_trace_item_push(dqu, dc);
}
#else
(void)dc_flags;
#endif
return dx_push(dqu._dq, dc, qos);
}
我們知道如果當(dāng)前是GCD的調(diào)用會走_dispatch_trace_item_push
,所以進入到源碼一探究竟
_dispatch_trace_item_push(dispatch_queue_class_t dqu, dispatch_object_t _tail)
{
_dispatch_trace_item_push_inline(dqu._dq, _tail._do);
_dispatch_introspection_queue_push(dqu, _tail);
}
順著源碼進入第一個函數(shù)去初探行瑞,我們能看到是一個任務(wù)隊列封裝器
奸腺,目的就是通過賦值和相應(yīng)的操作以返回一個全新的任務(wù)隊列。有興趣的朋友可以自行去研究了一下血久;而實際調(diào)用過程是第二個函數(shù)_dispatch_introspection_queue_push
static inline void
_dispatch_introspection_queue_push(dispatch_queue_class_t dqu,
dispatch_object_t dou)
{
_dispatch_introspection_queue_item_enqueue(dqu, dou);
}
再次進入底層_dispatch_introspection_queue_item_enqueue
_dispatch_introspection_queue_item_enqueue(dispatch_queue_t dq,
dispatch_object_t dou)
{
DISPATCH_INTROSPECTION_INTERPOSABLE_HOOK_CALLOUT(
queue_item_enqueue, dq, dou);
if (DISPATCH_INTROSPECTION_HOOK_ENABLED(queue_item_enqueue)) {
_dispatch_introspection_queue_item_enqueue_hook(dq, dou);
}
}
到最終我們看到調(diào)用的關(guān)鍵字段_dispatch_introspection_queue_item_enqueue_hook
_dispatch_introspection_queue_item_enqueue_hook(dispatch_queue_t dq,
dispatch_object_t dou)
{
dispatch_introspection_queue_item_s diqi;
diqi = dispatch_introspection_queue_item_get_info(dq, dou._dc);
dispatch_introspection_hook_callout_queue_item_enqueue(dq, &diqi);
}
我們看到最終異步函數(shù)的調(diào)用是通過封裝的宏定義函數(shù)執(zhí)行的
#define DISPATCH_INTROSPECTION_HOOK_CALLOUT(h, ...) ({ \
__typeof__(_dispatch_introspection_hooks.h) _h; \
_h = _dispatch_introspection_hooks.h; \
if (unlikely((void*)(_h) != DISPATCH_INTROSPECTION_NO_HOOK)) { \
_h(__VA_ARGS__); \
} })
總結(jié):異步函數(shù)的執(zhí)行流程是 dispatch_async -> _dispatch_continuation_async -> _dispatch_trace_item_push -> _dispatch_introspection_queue_push -> _dispatch_introspection_queue_item_enqueue -> _dispatch_introspection_queue_item_enqueue_hook -> DISPATCH_INTROSPECTION_HOOK_CALLOUT
三突照、隊列
我們都知道在iOS開發(fā)過程當(dāng)中或者是任何一門操作系統(tǒng)語言中,隊列
都是一個很重要的數(shù)據(jù)結(jié)構(gòu)氧吐,它有著FIFO(先進先出)
原則讹蘑,根據(jù)操作系統(tǒng)內(nèi)核的不同峡谊,隊列又劃分為串行隊列
和 并發(fā)隊列
坑匠。
接下來就讓我們從不同的地方取分析這兩種隊列辕羽。
3.1 串行隊列
串行隊列的概念
所有的調(diào)度任務(wù)進入到任務(wù)棧以后医窿,就由CPU統(tǒng)一調(diào)度绢记,最先進去的任務(wù)先調(diào)度吱窝,在調(diào)度任務(wù)未完成之前调衰,其他任務(wù)不能被調(diào)度养葵。這就是串行隊列心剥,也就是相當(dāng)于排隊買票邦尊,一個一個來的進行。
串行隊列的結(jié)構(gòu)
我們在測試Demo中創(chuàng)建iOS開發(fā)過程中的常用幾種隊列优烧,mainQueue
,serialQueue
,globalQueue
,以及concurrentQueue
, 并且答應(yīng)相關(guān)類的結(jié)構(gòu)可知
dispatch_queue_t serial = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
// OS_dispatch_queue_concurrent
dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
// DISPATCH_QUEUE_SERIAL max && 1
// queue 對象 alloc init class
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 多個 - 集合
dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
NSLog(@"%@-%@-%@-%@",serial,conque,mainQueue,globQueue);
打印結(jié)果如下
<OS_dispatch_queue_serial: cooci>-<OS_dispatch_queue_concurrent: cooci>-<OS_dispatch_queue_main: com.apple.main-thread>-<OS_dispatch_queue_global: com.apple.root.default-qos>
所以從上我們知道了主隊列也是串行隊列蝉揍,只是不同于一般的串行隊列而已,從iOS GCD文檔中已經(jīng)標(biāo)記的很明白了畦娄。那么串行隊列的底層源碼是如何實現(xiàn)的呢又沾?我們繼續(xù)探索
我們從以上的打印結(jié)果中知道,主隊列的打印結(jié)果是OS_dispatch_queue_main: com.apple.main-thread
,我們進入源碼搜索主線程的結(jié)果 能看到相應(yīng)的隊列定義
struct dispatch_queue_static_s _dispatch_main_q = {
DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
#if !DISPATCH_USE_RESOLVERS
.do_targetq = _dispatch_get_default_queue(true),
#endif
.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
DISPATCH_QUEUE_ROLE_BASE_ANON,
.dq_label = "com.apple.main-thread",
.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),
.dq_serialnum = 1,
};
3.2 并發(fā)隊列
并發(fā)隊列的概念:
所有的調(diào)度任務(wù)進入到任務(wù)棧以后熙卡,就由CPU統(tǒng)一調(diào)度杖刷,最先進去的任務(wù)先調(diào)度,但是由于計算機的CPU 在短暫的時間內(nèi)可以對多個調(diào)度任務(wù)進行處理驳癌,利用時間片輪轉(zhuǎn)發(fā)來進行任務(wù)的調(diào)度滑燃,所以就好像多個調(diào)度任務(wù)同時執(zhí)行的意思;
并發(fā)隊列的結(jié)構(gòu)
我們從以上的打印結(jié)果中知道颓鲜,主隊列的打印結(jié)果是OS_dispatch_queue_concurrent
,我們進入源碼搜索主線程的結(jié)果 能看到相應(yīng)的隊列定義
struct dispatch_queue_global_s _dispatch_root_queues[] = {
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
.dq_label = "com.apple.root.default-qos",
.dq_serialnum = 10,
),
_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
.dq_label = "com.apple.root.default-qos.overcommit",
.dq_serialnum = 11,
),
_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
.dq_label = "com.apple.root.user-initiated-qos",
.dq_serialnum = 12,
),
3.3 隊列如何創(chuàng)建的并且關(guān)聯(lián)類信息
我們在打印的結(jié)果中已經(jīng)知道串行隊列打印的結(jié)構(gòu)是OS_dispatch_queue_serial
表窘,并發(fā)隊列打印的結(jié)構(gòu)是OS_dispatch_queue_concurrent
,接下來就讓我們進入源碼看看隊列是如何關(guān)聯(lián)類對象并且isa指針的,
首先我們進入到dispatch_queue_create
函數(shù)看看源碼
dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
{
return _dispatch_lane_create_with_target(label, attr,
DISPATCH_TARGET_QUEUE_DEFAULT, true);
}
再次進入到_dispatch_lane_create_with_target
static dispatch_queue_t
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
{
// dqai 創(chuàng)建 -
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
//
// Step 1: Normalize arguments (qos, overcommit, tq)
//
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s)); // alloc
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0)); // init
dq->dq_label = label;
dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
dqai.dqai_relpri);
if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
}
if (!dqai.dqai_inactive) {
_dispatch_queue_priority_inherit_from_target(dq, tq);
_dispatch_lane_inherit_wlh_from_target(dq, tq);
}
_dispatch_retain(tq);
dq->do_targetq = tq;
_dispatch_object_debug(dq, "%s", __func__);
return _dispatch_trace_queue_create(dq)._dq;
就這樣能進行相關(guān)的隊列的創(chuàng)建過程甜滨;
以上創(chuàng)建過程中如果需要進行相應(yīng)的串行和并發(fā)的判斷
const void *vtable;
dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
if (dqai.dqai_concurrent) {
// OS_dispatch_queue_concurrent
vtable = DISPATCH_VTABLE(queue_concurrent);
} else {
vtable = DISPATCH_VTABLE(queue_serial);
}
然后我們進入到DISPATCH_VTABLE
中去看看是如何決定一個隊列是串行還是并發(fā)的
我們能找到相應(yīng)的宏定義是
define DISPATCH_VTABLE(name) DISPATCH_OBJC_CLASS(name)
再次搜索DISPATCH_OBJC_CLASS
我們也知道當(dāng)前是一個宏定義如下
#define OS_OBJECT_VTABLE(name) (&OS_OBJECT_CLASS_SYMBOL(name))
#define DISPATCH_OBJC_CLASS(name) (&DISPATCH_CLASS_SYMBOL(name))
我們再次全局搜索DISPATCH_CLASS_SYMBOL
就能找到定義如下
#define OS_OBJECT_EXTRA_VTABLE_SYMBOL(name) _OS_##name##_vtable
#define DISPATCH_CLASS_SYMBOL(name) OS_dispatch_##name##_class
從以上的源碼定義我們就知道系統(tǒng)是通過拼接名字進行的
通過以上創(chuàng)建的類進行名字拼接就得到我們自己所定義的類乐严。這就是隊列創(chuàng)建和關(guān)聯(lián)的過程。
四衣摩、總結(jié)
以上就是本人對隊列的創(chuàng)建和函數(shù)底層源碼的調(diào)用過程的學(xué)習(xí)麦备,由于libdispatch源碼晦澀難懂,所以我只能跟著源碼一步步查詢和學(xué)習(xí)昭娩,有很多不足之處凛篙,請大聲多多指教。