為什么dispatch_get_current_queue被廢棄

一、前言

根據(jù)dispatch_get_current_queue頭文件注釋

Recommended for debugging and logging purposes only: The code must not make any assumptions about the queue returned, unless it is one of the global queues or a queue the code has itself created. The code must not assume that synchronous execution onto a queue is safe from deadlock if that queue is not the one returned by dispatch_get_current_queue().

該方法早在iOS 6.0就已被廢棄,僅推薦用于調(diào)試和日志記錄,不能依賴(lài)函數(shù)返回值進(jìn)行邏輯判斷。具體為什么被廢棄,文檔并沒(méi)有詳細(xì)說(shuō)明,我們可以從GCD源碼找到些線(xiàn)索,代碼在libdispatch源碼可以下載。

二、隊(duì)列的數(shù)據(jù)結(jié)構(gòu)

創(chuàng)建隊(duì)列返回dispatch_queue_t類(lèi)型,dispatch_queue_s是dispatch_queue_t的別名

struct dispatch_queue_s {

    _DISPATCH_QUEUE_HEADER(queue);

    DISPATCH_QUEUE_CACHELINE_PADDING; // for static queues only

} DISPATCH_QUEUE_ALIGN;

結(jié)構(gòu)體內(nèi)的宏定義如下:

#define _DISPATCH_QUEUE_HEADER(x) \

    struct os_mpsc_queue_s _as_oq[0]; \

    DISPATCH_OBJECT_HEADER(x); \

    _OS_MPSC_QUEUE_FIELDS(dq, dq_state); \

    dispatch_queue_t dq_specific_q; \

    union { \

        uint32_t volatile dq_atomic_flags; \

        DISPATCH_STRUCT_LITTLE_ENDIAN_2( \

            uint16_t dq_atomic_bits, \

            uint16_t dq_width \

        ); \

    }; \

    uint32_t dq_side_suspend_cnt; \

    DISPATCH_INTROSPECTION_QUEUE_HEADER; \

    dispatch_unfair_lock_s dq_sidelock

    /* LP64: 32bit hole on LP64 */

#define DISPATCH_OBJECT_HEADER(x) \

    struct dispatch_object_s _as_do[0]; \

    _DISPATCH_OBJECT_HEADER(x)

#define _DISPATCH_OBJECT_HEADER(x) \

    struct _os_object_s _as_os_obj[0]; \

    OS_OBJECT_STRUCT_HEADER(dispatch_##x); \

    struct dispatch##x##s *volatile do_next; \

    struct dispatch_queue_s *do_targetq; \

    void *do_ctxt; \

    void *do_finalizer

可以看到GCD相關(guān)結(jié)構(gòu)體如dispatch_queue_s、dispatch_source_s都"繼承于"dispatch_object_t,把dispatch_object_t放在內(nèi)存布局的起始處即可實(shí)現(xiàn)這種"繼承"。dispatch_object_t中有個(gè)很重要的屬性do_targetq,稍后介紹這個(gè)屬性。

創(chuàng)建隊(duì)列過(guò)程:

_dispatch_queue_create_with_target(const char *label, dispatch_queue_attr_t dqa,

        dispatch_queue_t tq, bool legacy)

{

...

if (!tq) {

        qos_class_t tq_qos = qos == _DISPATCH_QOS_CLASS_UNSPECIFIED ?

                _DISPATCH_QOS_CLASS_DEFAULT : qos;

        tq = _dispatch_get_root_queue(tq_qos, overcommit ==

                _dispatch_queue_attr_overcommit_enabled);

        if (slowpath(!tq)) {

            DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");

        }

    }
...

dispatch_queue_t dq = _dispatch_alloc(vtable,

            sizeof(struct dispatch_queue_s) - DISPATCH_QUEUE_CACHELINE_PAD);

    _dispatch_queue_init(dq, dqf, dqa->dqa_concurrent ?

            DISPATCH_QUEUE_WIDTH_MAX : 1, dqa->dqa_inactive);

    dq->dq_label = label;

#if HAVE_PTHREAD_WORKQUEUE_QOS

    dq->dq_priority = (dispatch_priority_t)_pthread_qos_class_encode(qos,

            dqa->dqa_relative_priority,

            overcommit == _dispatch_queue_attr_overcommit_enabled ?

            _PTHREAD_PRIORITY_OVERCOMMIT_FLAG : 0);

#endif

    _dispatch_retain(tq);

    if (qos == _DISPATCH_QOS_CLASS_UNSPECIFIED) {

        // legacy way of inherithing the QoS from the target

        _dispatch_queue_priority_inherit_from_target(dq, tq);

    }

    if (!dqa->dqa_inactive) {

        _dispatch_queue_atomic_flags_set(tq, DQF_TARGETED);

    }

    dq->do_targetq = tq;

    ...

}

初始化dispatch_queue_s,do_targetq指向參數(shù)攜帶的target queue,如果沒(méi)有指定target queue,則指向root queue,那么這個(gè)root queue又是什么呢?

struct dispatch_queue_s _dispatch_root_queues[] = {
  ...
    _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE_QOS,

        .dq_label = "com.apple.root.maintenance-qos",

        .dq_serialnum = 4,

    ),

    _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE_QOS_OVERCOMMIT,

        .dq_label = "com.apple.root.maintenance-qos.overcommit",

        .dq_serialnum = 5,

    ),

    _DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND_QOS,

        .dq_label = "com.apple.root.background-qos",

        .dq_serialnum = 6,

    ),

     ...

};

root queue是一個(gè)數(shù)組,存放著不同QOS級(jí)別的隊(duì)列信息

我們熟悉的全局隊(duì)列定義如下:

dispatch_queue_t dispatch_get_global_queue(long priority, unsigned long flags)
{
  ...
   return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
}

就是根據(jù)參數(shù)取root queue取數(shù)組對(duì)應(yīng)項(xiàng),所以創(chuàng)建隊(duì)列默認(rèn)的target queue是root / global queue。

三、GCD隊(duì)列的層級(jí)結(jié)構(gòu)

從以上可以看出,派發(fā)隊(duì)列其實(shí)是按照層級(jí)結(jié)構(gòu)來(lái)組織的,引用Concurrent Programming: APIs and Challenges里的一張圖:

無(wú)論是串行還是并發(fā)隊(duì)列,只要有targetq,都會(huì)一層一層地往上扔,直到線(xiàn)程池。所以無(wú)法單用某個(gè)隊(duì)列對(duì)象來(lái)描述“當(dāng)前隊(duì)列”這一概念的,如下代碼:

dispatch_set_target_queue(queueB, queueA);
dispatch_sync(queueB, ^{
    dispatch_sync(queueA, ^{ /* deadlock! */ });
});

設(shè)置了B的target queue為A,那么以上代碼中A B都可以看成是當(dāng)前隊(duì)列。

四、dispatch_get_current_queue的誤用

void executeOnQueueSync(dispatch_queue_t queue , dispatch_block_t block) {
    if (dispatch_get_current_queue() == queue) {
        block();
    } else {
        dispatch_sync(queue, block);
    }
}

當(dāng)在同步執(zhí)行任務(wù)時(shí),執(zhí)行以上方法,可能會(huì)導(dǎo)致死鎖,由于隊(duì)列的層級(jí)特性,dispatch_get_current_queue返回結(jié)果可能與預(yù)期不一致。

五、怎么判斷當(dāng)前隊(duì)列是指定隊(duì)列?

可以使用dispatch_queue_set_specific和dispatch_get_specific系列函數(shù)

static const void * const SpecificKey = (const void*)&SpecificKey;

void executeOnQueueSync(dispatch_queue_t queue , dispatch_block_t block)
{
    if (dispatch_get_specific(SpecificKey) == (__bridge void *)(queue))
        block();
    else
        dispatch_sync(queue, block);
}

- (void)test
{
    dispatch_queue_t queue = dispatch_queue_create(@"com.iceguest.queue, DISPATCH_QUEUE_SERIAL);
    dispatch_queue_set_specific(queue, SpecificKey, (__bridge void *)(queue), NULL);
    dispatch_sync(xxqueue, ^{
      executeOnQueueSync(queue,  ^{NSLog(@"test"});
            });
    });
}

那么specific系列函數(shù)為什么可以判斷當(dāng)前隊(duì)列是指定隊(duì)列?

直接看dispatch_get_specific源碼

void *  dispatch_get_specific(const void *key)
{
    if (slowpath(!key)) {
        return NULL;
    }

    void *ctxt = NULL;
  
    dispatch_queue_t dq = _dispatch_queue_get_current();
  
    while (slowpath(dq)) {
        if (slowpath(dq->dq_specific_q)) {
            ctxt = (void *)key;
            dispatch_sync_f(dq->dq_specific_q, &ctxt,
                    _dispatch_queue_get_specific);
            if (ctxt) break;
        }
        dq = dq->do_targetq;
    }
    return ctxt;
}

這里也調(diào)用了_dispatch_queue_get_current函數(shù),得到一個(gè)當(dāng)前隊(duì)列,然后遍歷隊(duì)列的targetq,匹配到targetq的specific和參數(shù)提供的specific相等就返回,它的重要之處就在于如果根據(jù)指定的key獲取不到關(guān)聯(lián)數(shù)據(jù),就會(huì)沿著層級(jí)體系向上查找,直到找到數(shù)據(jù)或到達(dá)根隊(duì)列為止 ,dispatch_set_specific正是設(shè)置隊(duì)列的specific data,其過(guò)程可參考源碼不再贅述。
如React Native源碼里,判斷當(dāng)前是否在主隊(duì)列,就采用如下方法:

BOOL RCTIsMainQueue()
{
  static void *mainQueueKey = &mainQueueKey;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    dispatch_queue_set_specific(dispatch_get_main_queue(),
                                mainQueueKey, mainQueueKey, NULL);
  });
  return dispatch_get_specific(mainQueueKey) == mainQueueKey;
}

六、總結(jié)

  1. GCD隊(duì)列是按照層級(jí)結(jié)構(gòu)來(lái)組織的,無(wú)法單用某個(gè)隊(duì)列對(duì)象來(lái)描述“當(dāng)前隊(duì)列”
  2. dispatch_get_current_queue函數(shù)可能返回與預(yù)期不一致的結(jié)果
  3. 誤用dispatch_get_current_queue可能導(dǎo)致死鎖
  4. 設(shè)置隊(duì)列specific可以把任意數(shù)據(jù)以鍵值對(duì)的形式關(guān)聯(lián)到隊(duì)列里,從而得到需要的指定隊(duì)列
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容