iOS Objective-C GCD之queue(隊列)篇

iOS Objective-C GCD之queue(隊列)篇

GCD全稱Grand Central Dispatch,是蘋果為多核的并行運算提出的解決方案疗涉,由C語言實現(xiàn)亚侠,GCD會自動利用更多的CPU內(nèi)核什往,自動管理線程的聲明周期。GCD的底層實現(xiàn)來自libdispatch庫迎捺,我們可以在Apple Open Source下載各個版本的libdispatch源碼举畸。在這篇文章中我們著重介紹GCD中的隊列。

1. 隊列

隊列是一種特殊的線性表凳枝,特殊之處在于它只允許在表的前端(front)進行刪除操作抄沮,而在表的后端(rear)進行插入操作跋核,和棧一樣,隊列是一種操作受限制的線性表叛买。進行插入操作的端稱為隊尾砂代,進行刪除操作的端稱為隊頭。

在代碼編程中率挣,就是我們在隊尾插入一塊任務(wù)刻伊,然后Runloop調(diào)度線程去執(zhí)行任務(wù),執(zhí)行完就出隊椒功,繼續(xù)執(zhí)行下一個捶箱。這些任務(wù)可以是一個簡單的運算,也可以是加載一張圖片动漾,在iOS中也可以是一個block代碼塊丁屎。

在使用GCD的時候,我們都會獲取一個隊列旱眯,所以說GCD的使用離不開隊列晨川,實際上獲取的隊列的類型是dispatch_queue_t

隊列分為串行并行(并發(fā))

  • 串行隊列中任務(wù)只會順序執(zhí)行删豺,類似于公交車共虑,前門一個一個的上車,后門一個一個的下車
  • 并行隊列中任務(wù)可以并行執(zhí)行呀页,類似于火車或地鐵看蚜,可以多個門口上車,多個門口下車
隊列.jpg

在系統(tǒng)中隊列還分為全局隊列主隊列

  • 全局隊列 由系統(tǒng)創(chuàng)建赔桌,在iOS中蘋果給程序員提供了一個global_queue的全局并發(fā)隊列,渴逻,在多線程開發(fā)中如果沒有特殊需求疾党,在執(zhí)行異步任務(wù)的時候可以直接使用全局隊列。
  • 主隊列 同樣由系統(tǒng)創(chuàng)建惨奕,每個應(yīng)用程序?qū)?yīng)唯一一個主隊列雪位,主隊列與主線程是綁定的,在iOS中主隊列通常都是用來更新UI的梨撞。

1.1 iOS中創(chuàng)建隊列的方式

1.1.1 獲取主隊列

  • 獲取主隊列使用的函數(shù)dispatch_get_main_queue()
  • 主隊列是在應(yīng)用啟動的時雹洗,main函數(shù)執(zhí)行前系統(tǒng)自動創(chuàng)建的,這個隊列被綁定在主線程上卧波。

1.1.2 獲取全局隊列

  • 獲取全局隊列的函數(shù)dispatch_get_global_queue(<#intptr_t identifier#>, <#uintptr_t flags#>)
  • 第一個參數(shù)我們一般傳0时肿,但是系統(tǒng)也有一些枚舉值如下:
枚舉 優(yōu)先級
DISPATCH_QUEUE_PRIORITY_HIGH 2 高優(yōu)先級
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默認(rèn)優(yōu)先級
DISPATCH_QUEUE_PRIORITY_LOW -2 低優(yōu)先級
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后臺優(yōu)先級
  • 第二個參數(shù)是標(biāo)簽flag,我們在使用的時候傳0的時候也很多

1.1.3 創(chuàng)建一個新隊列

  • 創(chuàng)建新隊列使用的函數(shù):dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)
  • 第一個參數(shù)label用來標(biāo)識queue的字符串一般使用倒域名的形式"com.xxx.xxx"
  • 第二個參數(shù)attr隊列屬性港粱,DISPATCH_QUEUE_SERIAL也就是NULL會創(chuàng)建串行隊列螃成,DISPATCH_QUEUE_CONCURRENT會創(chuàng)建并發(fā)隊列旦签。

1.2 函數(shù)

將任務(wù)添加到隊列,并且指定執(zhí)行任務(wù)的函數(shù)

  • 任務(wù)使用block封裝寸宏,沒有參數(shù)也沒有返回值
  • 執(zhí)行任務(wù)的異步函數(shù):dispatch_async
    • 不用等待當(dāng)前語句執(zhí)行完畢就可以執(zhí)行下一條語句
    • 會開啟線程執(zhí)行block的任務(wù)
    • 異步是多線程的代名詞
  • 執(zhí)行任務(wù)的同步函數(shù)dispatch_sync
    • 必須等待當(dāng)前語句執(zhí)行完畢才會執(zhí)行下一條語句
    • 不會開啟線程
    • 在當(dāng)前線程執(zhí)行block任務(wù)

1.3 隊列和函數(shù)

同步函數(shù)和串行隊列:

  • 不會開啟線程在當(dāng)前線程執(zhí)行任務(wù)
  • 任務(wù)串行執(zhí)行宁炫,任務(wù)一個接著一個
  • 會產(chǎn)生阻塞

同步函數(shù)并發(fā)隊列:

  • 不會開啟線程,在當(dāng)前線程執(zhí)行任務(wù)
  • 任務(wù)一個接著一個

異步函數(shù)串行隊列:

  • 開啟一條心線程
  • 任務(wù)一個接著一個

異步函數(shù)并發(fā)隊列:

  • 開啟線程氮凝,并在當(dāng)前線程執(zhí)行任務(wù)
  • 任務(wù)異步執(zhí)行羔巢,沒有順序,CPU調(diào)度有關(guān)

1.4 死鎖

  • 在主線程使用同步函數(shù)等著執(zhí)行任務(wù)
  • 主隊列等著主線程的任務(wù)執(zhí)行完畢在執(zhí)行自己的任務(wù)
  • 主隊列和主線程相互等待就會造成死鎖

2. 隊列的底層原理

我們下載一份新的libdispath源碼進行查看罩阵,此處下載的是libdispatch-1173.40.5

2.1 主隊列

我們?nèi)炙阉?code>dispatch_get_main_queue(竿秆,加個(是為了方便查找,剔除無關(guān)項永脓。通過搜索結(jié)果我們可以篩選到如下代碼:

dispatch_queue_main_t
dispatch_get_main_queue(void)
{
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
}

我們可以看到主隊列的底層實現(xiàn)是由一個DISPATCH_GLOBAL_OBJECT的宏袍辞,傳入了dispatch_queue_main_t_dispatch_main_q兩個參數(shù),下面我們在全局搜索一下這個宏常摧。(如果用Visual Studio Code查看可以直接跳轉(zhuǎn))

全局搜索后可以得到下面三個結(jié)果:

// OS_OBJECT_USE_OBJC
#define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))

// defined(__cplusplus) && !defined(__DISPATCH_BUILDING_DISPATCH__)
#define DISPATCH_GLOBAL_OBJECT(type, object) (static_cast<type>(&(object)))

// /* Plain C */
#define DISPATCH_GLOBAL_OBJECT(type, object) ((type)&(object))

分別對應(yīng)注釋中的環(huán)境搅吁,在此處我們用的是第三個,純C 語言環(huán)境(如果使用VSCode也會直接跳轉(zhuǎn)到這行)落午。

我們試著再去點擊dispatch_queue_main_t進行跳轉(zhuǎn)谎懦,代碼如下:

#if defined(__DISPATCH_BUILDING_DISPATCH__) && !defined(__OBJC__)
typedef struct dispatch_queue_static_s *dispatch_queue_main_t;
#else
DISPATCH_DECL_SUBCLASS(dispatch_queue_main, dispatch_queue_serial);
#endif

對于objc環(huán)境是else里面的DISPATCH_DECL_SUBCLASS(dispatch_queue_main, dispatch_queue_serial);DISPATCH_DECL_SUBCLASS這個宏定義如下

#if OS_OBJECT_SWIFT3
#define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS_SWIFT(name, dispatch_object)
#define DISPATCH_DECL_SUBCLASS(name, base) OS_OBJECT_DECL_SUBCLASS_SWIFT(name, base)
#else // OS_OBJECT_SWIFT3
#define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS(name, dispatch_object)
#define DISPATCH_DECL_SUBCLASS(name, base) OS_OBJECT_DECL_SUBCLASS(name, base)

這里選擇后面這個非Swift3的溃斋,繼續(xù)查找OS_OBJECT_DECL_SUBCLASS定義如下:

#define OS_OBJECT_DECL_SUBCLASS(name, super) \
        OS_OBJECT_DECL_IMPL(name, <OS_OBJECT_CLASS(super)>)

這里就是一個IMP指針了界拦,應(yīng)該是系統(tǒng)定義好的,我們可以通過指針去取值梗劫。其實我們也可以看得出來主隊列就是serial的子類享甸,一個特殊的串行隊列。

2.2 全局隊列

老樣子梳侨,我們?nèi)炙阉?code>dispatch_get_global_queue(蛉威,最后通過篩選找到如下代碼:

dispatch_queue_global_t
dispatch_get_global_queue(long priority, unsigned long flags)
{
    dispatch_assert(countof(_dispatch_root_queues) ==
            DISPATCH_ROOT_QUEUE_COUNT);

    if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {
        return DISPATCH_BAD_INPUT;
    }
    dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);
#if !HAVE_PTHREAD_WORKQUEUE_QOS
    if (qos == QOS_CLASS_MAINTENANCE) {
        qos = DISPATCH_QOS_BACKGROUND;
    } else if (qos == QOS_CLASS_USER_INTERACTIVE) {
        qos = DISPATCH_QOS_USER_INITIATED;
    }
#endif
    if (qos == DISPATCH_QOS_UNSPECIFIED) {
        return DISPATCH_BAD_INPUT;
    }
    return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
}

可以看出該處代碼就是一層封裝,進行一些容錯處理走哺,然后獲取到優(yōu)先級(qos)蚯嫌,最后調(diào)用了_dispatch_get_root_queue

_dispatch_get_root_queue 源碼:

static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
    if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
        DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");
    }
    return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}

我們可以在_dispatch_get_root_queue中看出首先是對優(yōu)先級的一個容錯判斷,然后從一個數(shù)組中取出一個隊列返回丙躏。那么我們就來看看這個數(shù)組择示,搜索很麻煩,我用VSCode跳轉(zhuǎn)找到的晒旅。源碼如下:

struct dispatch_queue_global_s _dispatch_root_queues[] = {
#define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \
        ((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \
        DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \
        DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)
#define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \
    [_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \
        DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \
        .dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \
        .do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \
        .dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
        .dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \
                _dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \
                _dispatch_priority_make(DISPATCH_QOS_##n, 0)), \
        __VA_ARGS__ \
    }
    _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,
        .dq_label = "com.apple.root.maintenance-qos",
        .dq_serialnum = 4,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.maintenance-qos.overcommit",
        .dq_serialnum = 5,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,
        .dq_label = "com.apple.root.background-qos",
        .dq_serialnum = 6,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.background-qos.overcommit",
        .dq_serialnum = 7,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,
        .dq_label = "com.apple.root.utility-qos",
        .dq_serialnum = 8,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.utility-qos.overcommit",
        .dq_serialnum = 9,
    ),
    _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,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.user-initiated-qos.overcommit",
        .dq_serialnum = 13,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,
        .dq_label = "com.apple.root.user-interactive-qos",
        .dq_serialnum = 14,
    ),
    _DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
        .dq_label = "com.apple.root.user-interactive-qos.overcommit",
        .dq_serialnum = 15,
    ),
};

可以看到這個數(shù)組中定義了很多隊列供我們使用栅盲,根據(jù)優(yōu)先級和overcommit的值通過相關(guān)計算得出需要取得數(shù)組中第幾個隊列。

下面我們通過代碼驗證一下:

驗證代碼:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    NSLog(@"global queue: %@", [NSThread currentThread]);
});
調(diào)用堆棧.jpg

我們可以從調(diào)用堆棧里看出敢朱,該代碼執(zhí)行線程所用的隊列是com.apple.root.default-qos (concurrent)剪菱,對應(yīng)數(shù)組中如下:

_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
        .dq_label = "com.apple.root.default-qos",
        .dq_serialnum = 10,
    )

當(dāng)我們把優(yōu)先級換成DISPATCH_QUEUE_PRIORITY_BACKGROUND結(jié)果如下:隊列為com.apple.root.background-qos (concurrent)

調(diào)用堆棧.jpg

其實我們會有個問題摩瞎?我們不是在探索隊列的底層原理嗎?也就是在找隊列是如何創(chuàng)建的孝常,那么我們從數(shù)組中取出的隊列是如何創(chuàng)建的呢旗们?我們只能搜索_dispatch_root_queues了,好在不是很多构灸,經(jīng)過我們一個一個的排查上渴,最后找到了_dispatch_introspection_init函數(shù),在這里通過循環(huán)遍歷調(diào)用_dispatch_trace_queue_create函數(shù)一個一個的取出數(shù)組里的地址指針創(chuàng)建的隊列喜颁。這里還有個小插曲稠氮,我一開始找到了一個_dispatch_root_queues_init函數(shù),其內(nèi)部調(diào)用了_dispatch_root_queues_init_once函數(shù)半开,內(nèi)部也有個類似的循環(huán)取出_dispatch_root_queues數(shù)組中元素去init的操作隔披,但是仔細一看這里并不是創(chuàng)建隊列,而是調(diào)用_dispatch_root_queue_init_pthread_pool為每個隊列初始化線程池寂拆。

_dispatch_introspection_init源碼(創(chuàng)建數(shù)組中的隊列):

void
_dispatch_introspection_init(void)
{
    _dispatch_introspection.debug_queue_inversions =
            _dispatch_getenv_bool("LIBDISPATCH_DEBUG_QUEUE_INVERSIONS", false);

    // Hack to determine queue TSD offset from start of pthread structure
    uintptr_t thread = _dispatch_thread_self();
    thread_identifier_info_data_t tiid;
    mach_msg_type_number_t cnt = THREAD_IDENTIFIER_INFO_COUNT;
    kern_return_t kr = thread_info(pthread_mach_thread_np((void*)thread),
            THREAD_IDENTIFIER_INFO, (thread_info_t)&tiid, &cnt);
    if (!dispatch_assume_zero(kr)) {
        _dispatch_introspection.thread_queue_offset =
                (void*)(uintptr_t)tiid.dispatch_qaddr - (void*)thread;
    }
    _dispatch_thread_key_create(&dispatch_introspection_key,
            _dispatch_introspection_thread_remove);
    _dispatch_introspection_thread_add(); // add main thread

    for (size_t i = 0; i < DISPATCH_ROOT_QUEUE_COUNT; i++) {
        _dispatch_trace_queue_create(&_dispatch_root_queues[i]);
    }
#if DISPATCH_USE_MGR_THREAD && DISPATCH_USE_PTHREAD_ROOT_QUEUES
    _dispatch_trace_queue_create(_dispatch_mgr_q.do_targetq);
#endif
    _dispatch_trace_queue_create(&_dispatch_main_q);
    _dispatch_trace_queue_create(&_dispatch_mgr_q);
}

_dispatch_trace_queue_create的實現(xiàn)我們放在后面在進一步分析奢米。

我們搜索_dispatch_introspection_init可以發(fā)現(xiàn)他的調(diào)用是在libdispatch_init函數(shù)中,在這個函數(shù)中還調(diào)用了如下的初始化函數(shù)

_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();
_voucher_init();
_dispatch_introspection_init();

2.3 自我創(chuàng)建的隊列

我們知道隊列分為串行和并行纠永,那么我們就分別創(chuàng)建下鬓长,然后打印一下他們的信息。也順帶打印了一下mainQueueglobalQueue

po打印.jpg
名稱 類型 target width
main mian com.apple.root.default-qos.overcommit 0x1
global global \ 0xfff
serial serial com.apple.root.default-qos.overcommit 0x1
concurrent concurrent com.apple.root.default-qos 0xffe

我們可以看到不同隊列對應(yīng)的都是OS_dispatch_queue_XXX類型尝江,其中主隊列和serial隊列的target是一樣的涉波,在width方面主隊列與serial隊列依舊一致,可以說主隊列是一個特殊的串行隊列炭序;global的值是0xfff,concurrent的值是0xffe相對于global少了一個啤覆,那么為什么會這樣呢?我們馬上開始探索惭聂。

首先我們?nèi)炙阉饕幌?code>dispatch_queue_create(const城侧,來看看dispatch_queue_create是如何實現(xiàn)的。

dispatch_queue_create源碼:

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);
}

我們發(fā)現(xiàn)dispatch_queue_create就是一個隔離的操作彼妻,隊列的創(chuàng)建實際上是由_dispatch_lane_create_with_target函數(shù)來實現(xiàn)的,大概有120多行代碼豆茫。這里就不上代碼了侨歉,感興趣的可以下載源碼去查看,我們挑重點來分析揩魂。


dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

首先是上面這行代碼幽邓,一進來就通過傳入的并行和串行的參數(shù)初始化一個dispatch_queue_attr_info_t類型的dqai。那么dispatch_queue_attr_info_t是個什么東西呢火脉?我們可以通過全局搜索或者VSCode跳轉(zhuǎn)進行查看牵舵。

struct dispatch_queue_attr_s {
    OS_OBJECT_STRUCT_HEADER(dispatch_queue_attr);
};

typedef struct dispatch_queue_attr_info_s {
    dispatch_qos_t dqai_qos : 8;
    int      dqai_relpri : 8;
    uint16_t dqai_overcommit:2;
    uint16_t dqai_autorelease_frequency:2;
    uint16_t dqai_concurrent:1;
    uint16_t dqai_inactive:1;
} dispatch_queue_attr_info_t;

通過查看我們可以知道dispatch_queue_attr_info_t是一個結(jié)構(gòu)體位域柒啤,結(jié)構(gòu)體位域可以通過位運算得到我們想要的內(nèi)容,過濾掉我們不想要的數(shù)據(jù)畸颅。下面我們在來看看這個結(jié)構(gòu)體是如何創(chuàng)建的吧担巩。

_dispatch_queue_attr_to_info源碼:

dispatch_queue_attr_info_t
_dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
{
    dispatch_queue_attr_info_t dqai = { };

    if (!dqa) return dqai;

#if DISPATCH_VARIANT_STATIC
    if (dqa == &_dispatch_queue_attr_concurrent) {
        dqai.dqai_concurrent = true;
        return dqai;
    }
#endif

    if (dqa < _dispatch_queue_attrs ||
            dqa >= &_dispatch_queue_attrs[DISPATCH_QUEUE_ATTR_COUNT]) {
        DISPATCH_CLIENT_CRASH(dqa->do_vtable, "Invalid queue attribute");
    }

    size_t idx = (size_t)(dqa - _dispatch_queue_attrs);

    dqai.dqai_inactive = (idx % DISPATCH_QUEUE_ATTR_INACTIVE_COUNT);
    idx /= DISPATCH_QUEUE_ATTR_INACTIVE_COUNT;

    dqai.dqai_concurrent = !(idx % DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT);
    idx /= DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT;

    dqai.dqai_relpri = -(int)(idx % DISPATCH_QUEUE_ATTR_PRIO_COUNT);
    idx /= DISPATCH_QUEUE_ATTR_PRIO_COUNT;

    dqai.dqai_qos = idx % DISPATCH_QUEUE_ATTR_QOS_COUNT;
    idx /= DISPATCH_QUEUE_ATTR_QOS_COUNT;

    dqai.dqai_autorelease_frequency =
            idx % DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
    idx /= DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;

    dqai.dqai_overcommit = idx % DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
    idx /= DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;

    return dqai;
}

_dispatch_queue_attr_to_info源碼中我們不難看出對于串行隊列也就dqaNULL的時候直接返回一個空的的dqai,其中有一個idx是蘋果的一種算法得到的一個值没炒,用于對后續(xù)的結(jié)構(gòu)體進行一系列的默認(rèn)配置和賦值涛癌,這里我們著重關(guān)注一下dqai.dqai_concurrent,對于并行隊列它是有值的送火,后面會用到拳话。取得dqai后,我們回到_dispatch_lane_create_with_target函數(shù)繼續(xù)分析种吸。經(jīng)過一系列的容錯分析后我們來到一處具有分水嶺意義的代碼:

const void *vtable;
    dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
    if (dqai.dqai_concurrent) {
        vtable = DISPATCH_VTABLE(queue_concurrent);
    } else {
        vtable = DISPATCH_VTABLE(queue_serial);
    }

也就是我們上面提到dqai.dqai_concurrent如果有值就創(chuàng)建并行隊列弃衍,沒值就是串行隊列。這里通過對vtable賦不同的值來區(qū)分坚俗,然后通過vtable開辟內(nèi)存镜盯,生成dq(dispatch queue),代碼如下:

dispatch_lane_t dq = _dispatch_object_alloc(vtable,
            sizeof(struct dispatch_lane_s));

然后通過_dispatch_queue_init函數(shù)進一步初始化我們的dq坦冠。

_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
            DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
            (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));

我們可以看到_dispatch_queue_init的第三參數(shù)在上面的代碼中通過一個三目運算取不同的值進行傳入形耗,判斷條件就是dqai.dqai_concurrent,其實就是串行隊列取1并行隊列是個宏DISPATCH_QUEUE_WIDTH_MAX辙浑,我們跳轉(zhuǎn)到這個宏(VSCode跳轉(zhuǎn)的)激涤,代碼如下:

#define DISPATCH_QUEUE_WIDTH_FULL_BIT       0x0020000000000000ull
#define DISPATCH_QUEUE_WIDTH_FULL           0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX  (DISPATCH_QUEUE_WIDTH_FULL - 2)
#define DISPATCH_QUEUE_USES_REDIRECTION(width) \
        ({ uint16_t _width = (width); \
        _width > 1 && _width < DISPATCH_QUEUE_WIDTH_POOL; })

可以看到這個值跟我們上面打印的width的值不謀而合,對于全局并發(fā)隊列是0x1000-1 = 0xfff判呕,對于自定義的并發(fā)隊列值是0x1000-2 = 0xffe倦踢。如果想要驗證DISPATCH_QUEUE_WIDTH_POOL也就是0x1000-1 = 0xfff是globalwidth可以直接全局搜索驗證,這里就不多說了侠草。

我們繼續(xù)往下看辱挥,_dispatch_queue_init的第四個參數(shù)是初始狀態(tài)位,因為隊列是有狀態(tài)的就是在這里初始化的边涕。接下來就是將label的值和優(yōu)先級``賦值給dq晤碘,以及對隊列的是否是就緒狀態(tài)是對優(yōu)先級的處理,還有不是補不活躍狀態(tài)的隊列也要進一步處理功蜓,代碼如下:

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;

這個tq是傳入的园爷,這里我們傳入的時候是DISPATCH_TARGET_QUEUE_DEFAULT也就是NULL,那么這里的tq是在哪里賦值的呢式撼?我們回過頭去找一找童社,在代碼中很多判斷tq的地方,但都因為tqNULL所以都沒有進入相關(guān)分支著隆,經(jīng)過我們查找,在如下代碼處給tq賦了值

if (!tq) {
        tq = _dispatch_get_root_queue(
                qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
                overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
        if (unlikely(!tq)) {
            DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
        }
    }

_dispatch_get_root_queue源碼:

static inline dispatch_queue_global_t
_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
{
    if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
        DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");
    }
    return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
}

還是上面global queue時的代碼,也是從_dispatch_root_queues數(shù)組中取出的值摄杂,所以說tq中記錄了dq_label的值泉孩,也就是我們一開始打印時的target的值。

知道tq是如何取值后我們繼續(xù)分析,最后調(diào)用了return _dispatch_trace_queue_create(dq)._dq;返回創(chuàng)建的隊列,這個函數(shù)在我們講解global queue的時候,對于_dispatch_root_queues數(shù)組中的隊列創(chuàng)建的時候也是最終調(diào)用了該函數(shù)忘嫉,那么我們就接著看看這個函數(shù)都做了什么事情吧。

DISPATCH_ALWAYS_INLINE
static inline dispatch_queue_class_t
_dispatch_trace_queue_create(dispatch_queue_class_t dqu)
{
    _dispatch_only_if_ktrace_enabled({
        uint64_t dq_label[4] = {0}; // So that we get the right null termination
        dispatch_queue_t dq = dqu._dq;
        strncpy((char *)dq_label, (char *)dq->dq_label ?: "", sizeof(dq_label));

        _dispatch_ktrace2(DISPATCH_QOS_TRACE_queue_creation_start,
                dq->dq_serialnum,
                _dispatch_priority_to_pp_prefer_fallback(dq->dq_priority));

        _dispatch_ktrace4(DISPATCH_QOS_TRACE_queue_creation_end,
                        dq_label[0], dq_label[1], dq_label[2], dq_label[3]);
    });

    return _dispatch_introspection_queue_create(dqu);
}

_dispatch_trace_queue_create源碼中案腺,主要分兩部分庆冕,一部分是系統(tǒng)調(diào)試,另一部分是調(diào)用_dispatch_introspection_queue_create函數(shù)劈榨。調(diào)試就不看了访递,下面我們就來看看_dispatch_introspection_queue_create函數(shù)的內(nèi)容。

_dispatch_introspection_queue_create源碼:

dispatch_queue_class_t
_dispatch_introspection_queue_create(dispatch_queue_t dq)
{
    dispatch_queue_introspection_context_t dqic;
    size_t sz = sizeof(struct dispatch_queue_introspection_context_s);

    if (!_dispatch_introspection.debug_queue_inversions) {
        sz = offsetof(struct dispatch_queue_introspection_context_s,
                __dqic_no_queue_inversion);
    }
    dqic = _dispatch_calloc(1, sz);
    dqic->dqic_queue._dq = dq;
    if (_dispatch_introspection.debug_queue_inversions) {
        LIST_INIT(&dqic->dqic_order_top_head);
        LIST_INIT(&dqic->dqic_order_bottom_head);
    }
    dq->do_finalizer = dqic;

    _dispatch_unfair_lock_lock(&_dispatch_introspection.queues_lock);
    LIST_INSERT_HEAD(&_dispatch_introspection.queues, dqic, dqic_list);
    _dispatch_unfair_lock_unlock(&_dispatch_introspection.queues_lock);

    DISPATCH_INTROSPECTION_INTERPOSABLE_HOOK_CALLOUT(queue_create, dq);
    if (DISPATCH_INTROSPECTION_HOOK_ENABLED(queue_create)) {
        _dispatch_introspection_queue_create_hook(dq);
    }
    return upcast(dq)._dqu;
}

dispatch_queue_introspection_context_t & dispatch_queue_introspection_context_s 結(jié)構(gòu)體

typedef struct dispatch_queue_introspection_context_s {
    dispatch_queue_class_t dqic_queue;
    dispatch_function_t dqic_finalizer;
    LIST_ENTRY(dispatch_queue_introspection_context_s) dqic_list;

    char __dqic_no_queue_inversion[0];

    // used for queue inversion debugging only
    dispatch_unfair_lock_s dqic_order_top_head_lock;
    dispatch_unfair_lock_s dqic_order_bottom_head_lock;
    LIST_HEAD(, dispatch_queue_order_entry_s) dqic_order_top_head;
    LIST_HEAD(, dispatch_queue_order_entry_s) dqic_order_bottom_head;
} *dispatch_queue_introspection_context_t;

_dispatch_introspection_queue_create函數(shù)中

  1. 首先定義了一個dispatch_queue_introspection_context_t的上下文同辣。代碼也放在上面了拷姿。
  2. 然后獲取了dispatch_queue_introspection_context_s結(jié)構(gòu)體占用內(nèi)存的大小。
  3. 然后判斷非自我觀察調(diào)試隊列倒置(翻譯過來的旱函,我也不知道啥意思)响巢,反正就是這種情況下對上一步取出的sz偏移
  4. 然后調(diào)用_dispatch_calloc函數(shù)分配內(nèi)存
  5. 將傳入的dq賦值給一開始定義的dqicdqic_queue._dq
  6. 還是3中的那個判斷,開始監(jiān)聽隊列的頭尾(這里應(yīng)該是入隊和出隊)時添加任務(wù)到隊列尾棒妨,從隊列頭取出任務(wù)開始執(zhí)行(個人猜想)
  7. dqic賦值給傳入的dqdo_finalizer(終結(jié)器踪古,應(yīng)該是終止隊列時使用的)
  8. 加鎖插入創(chuàng)建的隊列到系統(tǒng)管理的隊列里面?在解鎖
  9. 一些hook處理(不是很明白)
  10. 返回

由于能力有限券腔,此處分析的亂七八糟伏穆,還請大神們指點。纷纫,其實到這里也就基本分析完了隊列的整個創(chuàng)建過程枕扫,在不同操作系統(tǒng),對于不同架構(gòu)的CPU辱魁,隊列的差別肯定是有的烟瞧,但是原理應(yīng)該是一樣的。感覺分析libdispath的源碼還是挺難的染簇,比較隊列線程這種東西屬于很底層的東西了燕刻。

3. 一些定義

3.1 dispatch_queue_t

分析了半天,其實我們還沒有仔細的分析dispatch_queue_t到底是個什么東西呢 剖笙?

我們點擊跳轉(zhuǎn)后跳轉(zhuǎn)到DISPATCH_DECL(dispatch_queue);這樣一行代碼處,這顯然不是我們想要的请唱。這個時候我們可以看看我們的創(chuàng)建隊列的方法時返回值處的代碼return _dispatch_trace_queue_create(dq)._dq;這個_dq就是我們要返回的值弥咪,前面的_dispatch_trace_queue_create函數(shù)的返回值類型是dispatch_queue_class_t定義如下:

// Dispatch queue cluster class: type for any dispatch_queue_t
typedef union {
    struct dispatch_queue_s *_dq;
    struct dispatch_workloop_s *_dwl;
    struct dispatch_lane_s *_dl;
    struct dispatch_queue_static_s *_dsq;
    struct dispatch_queue_global_s *_dgq;
    struct dispatch_queue_pthread_root_s *_dpq;
    struct dispatch_source_s *_ds;
    struct dispatch_channel_s *_dch;
    struct dispatch_mach_s *_dm;
    dispatch_lane_class_t _dlu;
#ifdef __OBJC__
    id<OS_dispatch_queue> _objc_dq;
#endif
} dispatch_queue_class_t DISPATCH_TRANSPARENT_UNION;

這是個聯(lián)合體过蹂,成員_dqdispatch_queue_s類型,其定義如下:

struct dispatch_queue_s {
    DISPATCH_QUEUE_CLASS_HEADER(queue, void *__dq_opaque1);
    /* 32bit hole on LP64 */
} DISPATCH_ATOMIC64_ALIGN;

接下來我們繼續(xù)看DISPATCH_QUEUE_CLASS_HEADER聚至,這是個宏酷勺,定義如下:

#define DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
    _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__); \
    /* LP64 global queue cacheline boundary */ \
    unsigned long dq_serialnum; \
    const char *dq_label; \
    DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \
        const uint16_t dq_width, \
        const uint16_t __dq_opaque2 \
    ); \
    dispatch_priority_t dq_priority; \
    union { \
        struct dispatch_queue_specific_head_s *dq_specific_head; \
        struct dispatch_source_refs_s *ds_refs; \
        struct dispatch_timer_source_refs_s *ds_timer_refs; \
        struct dispatch_mach_recv_refs_s *dm_recv_refs; \
        struct dispatch_channel_callbacks_s const *dch_callbacks; \
    }; \
    int volatile dq_sref_cnt

這個宏定義了一些通用屬性,包含隊列的所有成員扳躬,例如編號脆诉、label、隊列寬度贷币、優(yōu)先級等等击胜。

那么回到問題本身,我們的dispatch_queue_t是在什么時候定義的呢役纹?我們嘗試搜索了一下dispatch_queue_t;(其實也搜索了dispatch_source_s前后都有空格)找到如下代碼:

typedef struct dispatch_continuation_s *dispatch_continuation_t;
typedef struct dispatch_queue_s *dispatch_queue_t;
typedef struct dispatch_source_s *dispatch_source_t;
typedef struct dispatch_group_s *dispatch_group_t;
typedef struct dispatch_object_s *dispatch_object_t;

在這里我們可以清楚的看到dispatch_queue_t是一個dispatch_queue_s類型的結(jié)構(gòu)體指針偶摔。至此我們就找到了dispatch_queue_t的定義。

3.2 dispatch_object_t

在探索過程中我們還多次看到了dispatch_object_t的身影促脉,其實它的定義就在dispatch_queue_class_t的下面辰斋,如果我們直接搜索dispatch_object_t會看到很多關(guān)于它的定義,為什么我會認(rèn)為dispatch_queue_class_t下面代碼處使我們想要的呢瘸味?因為有個#ifndef __OBJC__

dispatch_object_t定義:

typedef union {
    struct _os_object_s *_os_obj;
    struct dispatch_object_s *_do;
    struct dispatch_queue_s *_dq;
    struct dispatch_queue_attr_s *_dqa;
    struct dispatch_group_s *_dg;
    struct dispatch_source_s *_ds;
    struct dispatch_channel_s *_dch;
    struct dispatch_mach_s *_dm;
    struct dispatch_mach_msg_s *_dmsg;
    struct dispatch_semaphore_s *_dsema;
    struct dispatch_data_s *_ddata;
    struct dispatch_io_s *_dchannel;

    struct dispatch_continuation_s *_dc;
    struct dispatch_sync_context_s *_dsc;
    struct dispatch_operation_s *_doperation;
    struct dispatch_disk_s *_ddisk;
    struct dispatch_workloop_s *_dwl;
    struct dispatch_lane_s *_dl;
    struct dispatch_queue_static_s *_dsq;
    struct dispatch_queue_global_s *_dgq;
    struct dispatch_queue_pthread_root_s *_dpq;
    dispatch_queue_class_t _dqu;
    dispatch_lane_class_t _dlu;
    uintptr_t _do_value;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;

我們發(fā)現(xiàn)dispatch_object_t是一個聯(lián)合體宫仗,使用聯(lián)合體的成員互斥性,同時只存在一種類型帶來了多態(tài)性旁仿。我們可以用一個dispatch_object_tGCD中表示所有的類型藕夫。

4. 總結(jié)

至此我們的GCD隊列篇就分析完畢了下面我們稍作總結(jié)

  1. 在iOS中可以獲取主隊列,全局并行隊列丁逝,還可以自己創(chuàng)建隊列汁胆,自己創(chuàng)建隊列可以是串行的也可以是并行;
  2. 主隊列和串行隊列的寬度都是1霜幼,全局并行隊列的寬度是0x1000-1 = 0xfff嫩码,自己創(chuàng)建的并行隊列的寬度是0x1000-2 = 0xffe;
  3. 主隊列底層是由DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q)獲取到的罪既,暫時沒有找到其開源的具體實現(xiàn)
  4. 全局并發(fā)隊列是從_dispatch_root_queues數(shù)組中取出的
  5. 關(guān)于自我創(chuàng)建的隊列也是從_dispatch_root_queues數(shù)組中取出一個對應(yīng)的值铸题,賦值給tq,然后創(chuàng)建響應(yīng)的隊列
  6. 關(guān)于_dispatch_root_queues數(shù)組中的隊列是在_dispatch_introspection_init函數(shù)中循環(huán)遍歷創(chuàng)建的琢感,_dispatch_introspection_init是由libdispatch_init函數(shù)調(diào)用的丢间。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市驹针,隨后出現(xiàn)的幾起案子烘挫,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饮六,死亡現(xiàn)場離奇詭異其垄,居然都是意外死亡,警方通過查閱死者的電腦和手機卤橄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門绿满,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窟扑,你說我怎么就攤上這事喇颁。” “怎么了嚎货?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵橘霎,是天一觀的道長。 經(jīng)常有香客問我厂抖,道長茎毁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任忱辅,我火速辦了婚禮七蜘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘墙懂。我一直安慰自己橡卤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布损搬。 她就那樣靜靜地躺著碧库,像睡著了一般。 火紅的嫁衣襯著肌膚如雪巧勤。 梳的紋絲不亂的頭發(fā)上嵌灰,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音颅悉,去河邊找鬼沽瞭。 笑死,一個胖子當(dāng)著我的面吹牛剩瓶,可吹牛的內(nèi)容都是我干的驹溃。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼延曙,長吁一口氣:“原來是場噩夢啊……” “哼豌鹤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起枝缔,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤布疙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灵临,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡拣挪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了俱诸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡赊舶,死狀恐怖睁搭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笼平,我是刑警寧澤园骆,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站寓调,受9級特大地震影響锌唾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夺英,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一晌涕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧痛悯,春花似錦余黎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扭仁,卻和暖如春垮衷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乖坠。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工搀突, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓤帚。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓描姚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親戈次。 傳聞我的和親對象是個殘疾皇子轩勘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345