GCD之函數(shù)與隊列初探

一轰异、前言

在iOS開發(fā)過程中,我們知道多線程技術(shù)是使用最多的情況暑始,能快速的執(zhí)行多個調(diào)度任務(wù)的執(zhí)行搭独。而在多線程開發(fā)過程當(dāng)中,多線程技術(shù)有好幾種廊镜,其中包括pthread牙肝,NSThreadNSOperationGCD,而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:

系統(tǒng)庫定位.png

通過以上我們就知道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).png
串行隊列的結(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).png
并發(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í)昭娩,有很多不足之處凛篙,請大聲多多指教。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末栏渺,一起剝皮案震驚了整個濱河市呛梆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌磕诊,老刑警劉巖填物,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纹腌,死亡現(xiàn)場離奇詭異,居然都是意外死亡滞磺,警方通過查閱死者的電腦和手機升薯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來击困,“玉大人涎劈,你說我怎么就攤上這事≡牟瑁” “怎么了蛛枚?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脸哀。 經(jīng)常有香客問我蹦浦,道長,這世上最難降的妖魔是什么撞蜂? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任盲镶,我火速辦了婚禮,結(jié)果婚禮上蝌诡,老公的妹妹穿的比我還像新娘徒河。我一直安慰自己,他們只是感情好送漠,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布顽照。 她就那樣靜靜地躺著,像睡著了一般闽寡。 火紅的嫁衣襯著肌膚如雪代兵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天爷狈,我揣著相機與錄音植影,去河邊找鬼。 笑死涎永,一個胖子當(dāng)著我的面吹牛思币,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播羡微,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼谷饿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了妈倔?” 一聲冷哼從身側(cè)響起博投,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盯蝴,沒想到半個月后毅哗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體听怕,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年虑绵,在試婚紗的時候發(fā)現(xiàn)自己被綠了尿瞭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡翅睛,死狀恐怖声搁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宏所,我是刑警寧澤酥艳,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布摊溶,位于F島的核電站爬骤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏莫换。R本人自食惡果不足惜霞玄,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拉岁。 院中可真熱鬧坷剧,春花似錦、人聲如沸喊暖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陵叽。三九已至狞尔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巩掺,已是汗流浹背偏序。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留胖替,地道東北人研儒。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像独令,于是被迫代替她去往敵國和親端朵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353