為什么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)試和日志記錄,不能依賴函數(shù)返回值進行邏輯判斷饼问。具體為什么被廢棄芯肤,文檔并沒有詳細說明树肃,我們可以從GCD源碼找到些線索,代碼在libdispatch源碼可以下載浅悉。

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

創(chuàng)建隊列返回dispatch_queue_t類型券犁,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)存布局的起始處即可實現(xiàn)這種"繼承"粘衬。dispatch_object_t中有個很重要的屬性do_targetq荞估,稍后介紹這個屬性咳促。

創(chuàng)建隊列過程:

_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勘伺,如果沒有指定target queue跪腹,則指向root queue,那么這個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是一個數(shù)組尺迂,存放著不同QOS級別的隊列信息

我們熟悉的全局隊列定義如下:

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ù)組對應項,所以創(chuàng)建隊列默認的target queue是root / global queue冒掌。

三噪裕、GCD隊列的層級結(jié)構(gòu)

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

無論是串行還是并發(fā)隊列膳音,只要有targetq,都會一層一層地往上扔铃诬,直到線程池祭陷。所以無法單用某個隊列對象來描述“當前隊列”這一概念的,如下代碼:

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

設(shè)置了B的target queue為A趣席,那么以上代碼中A B都可以看成是當前隊列兵志。

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

當在同步執(zhí)行任務時宣肚,執(zhí)行以上方法想罕,可能會導致死鎖,由于隊列的層級特性霉涨,dispatch_get_current_queue返回結(jié)果可能與預期不一致按价。

五、怎么判斷當前隊列是指定隊列笙瑟?

可以使用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ù)為什么可以判斷當前隊列是指定隊列楼镐?

直接看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ù),得到一個當前隊列往枷,然后遍歷隊列的targetq框产,匹配到targetq的specific和參數(shù)提供的specific相等就返回,它的重要之處就在于如果根據(jù)指定的key獲取不到關(guān)聯(lián)數(shù)據(jù)错洁,就會沿著層級體系向上查找茅信,直到找到數(shù)據(jù)或到達根隊列為止 ,dispatch_set_specific正是設(shè)置隊列的specific data墓臭,其過程可參考源碼不再贅述蘸鲸。
如React Native源碼里,判斷當前是否在主隊列窿锉,就采用如下方法:

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隊列是按照層級結(jié)構(gòu)來組織的膝舅,無法單用某個隊列對象來描述“當前隊列”
  2. dispatch_get_current_queue函數(shù)可能返回與預期不一致的結(jié)果
  3. 誤用dispatch_get_current_queue可能導致死鎖
  4. 設(shè)置隊列specific可以把任意數(shù)據(jù)以鍵值對的形式關(guān)聯(lián)到隊列里,從而得到需要的指定隊列
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窑多,一起剝皮案震驚了整個濱河市仍稀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌埂息,老刑警劉巖技潘,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異千康,居然都是意外死亡享幽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門拾弃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來值桩,“玉大人,你說我怎么就攤上這事豪椿”挤兀” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵搭盾,是天一觀的道長咳秉。 經(jīng)常有香客問我,道長鸯隅,這世上最難降的妖魔是什么澜建? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮滋迈,結(jié)果婚禮上霎奢,老公的妹妹穿的比我還像新娘户誓。我一直安慰自己饼灿,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布帝美。 她就那樣靜靜地躺著碍彭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悼潭。 梳的紋絲不亂的頭發(fā)上庇忌,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音舰褪,去河邊找鬼皆疹。 笑死,一個胖子當著我的面吹牛占拍,可吹牛的內(nèi)容都是我干的略就。 我是一名探鬼主播捎迫,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼表牢!你這毒婦竟也來了窄绒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤崔兴,失蹤者是張志新(化名)和其女友劉穎彰导,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敲茄,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡位谋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了折汞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倔幼。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖爽待,靈堂內(nèi)的尸體忽然破棺而出损同,到底是詐尸還是另有隱情,我是刑警寧澤鸟款,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布膏燃,位于F島的核電站,受9級特大地震影響何什,放射性物質(zhì)發(fā)生泄漏组哩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一处渣、第九天 我趴在偏房一處隱蔽的房頂上張望伶贰。 院中可真熱鬧,春花似錦罐栈、人聲如沸黍衙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琅翻。三九已至,卻和暖如春柑贞,著一層夾襖步出監(jiān)牢的瞬間方椎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工钧嘶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棠众,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓有决,卻偏偏與公主長得像闸拿,于是被迫代替她去往敵國和親轿亮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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