为什么dispatch_get_current_queue被废弃

一、前言

根据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就已被废弃,仅推荐用于调试和日志记录,不能依赖函数返回值进行逻辑判断。具体为什么被废弃,文档并没有详细说明,我们可以从GCD源码找到些线索,代码在libdispatch源码可以下载。

二、队列的数据结构

创建队列返回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;

结构体内的宏定义如下:

#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相关结构体如dispatch_queue_s、dispatch_source_s都"继承于"dispatch_object_t,把dispatch_object_t放在内存布局的起始处即可实现这种"继承"。dispatch_object_t中有个很重要的属性do_targetq,稍后介绍这个属性。

创建队列过程:

_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指向参数携带的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是一个数组,存放着不同QOS级别的队列信息

我们熟悉的全局队列定义如下:

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

就是根据参数取root queue取数组对应项,所以创建队列默认的target queue是root / global queue。

三、GCD队列的层级结构

从以上可以看出,派发队列其实是按照层级结构来组织的,引用Concurrent Programming: APIs and Challenges里的一张图:

无论是串行还是并发队列,只要有targetq,都会一层一层地往上扔,直到线程池。所以无法单用某个队列对象来描述“当前队列”这一概念的,如下代码:

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

设置了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);
    }
}

当在同步执行任务时,执行以上方法,可能会导致死锁,由于队列的层级特性,dispatch_get_current_queue返回结果可能与预期不一致。

五、怎么判断当前队列是指定队列?

可以使用dispatch_queue_set_specific和dispatch_get_specific系列函数

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系列函数为什么可以判断当前队列是指定队列?

直接看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;
}

这里也调用了_dispatch_queue_get_current函数,得到一个当前队列,然后遍历队列的targetq,匹配到targetq的specific和参数提供的specific相等就返回,它的重要之处就在于如果根据指定的key获取不到关联数据,就会沿着层级体系向上查找,直到找到数据或到达根队列为止 ,dispatch_set_specific正是设置队列的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;
}

六、总结

  1. GCD队列是按照层级结构来组织的,无法单用某个队列对象来描述“当前队列”
  2. dispatch_get_current_queue函数可能返回与预期不一致的结果
  3. 误用dispatch_get_current_queue可能导致死锁
  4. 设置队列specific可以把任意数据以键值对的形式关联到队列里,从而得到需要的指定队列
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容