美文网首页iOS 进阶iOS DeveloperiOS开发_核心
为什么dispatch_get_current_queue被废弃

为什么dispatch_get_current_queue被废弃

作者: Iceguest | 来源:发表于2017-06-28 21:27 被阅读357次

一、前言

根据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可以把任意数据以键值对的形式关联到队列里,从而得到需要的指定队列

相关文章

网友评论

    本文标题:为什么dispatch_get_current_queue被废弃

    本文链接:https://www.haomeiwen.com/subject/rjlpcxtx.html