美文网首页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