一、前言
根据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;
}
六、总结
- GCD队列是按照层级结构来组织的,无法单用某个队列对象来描述“当前队列”
- dispatch_get_current_queue函数可能返回与预期不一致的结果
- 误用dispatch_get_current_queue可能导致死锁
- 设置队列specific可以把任意数据以键值对的形式关联到队列里,从而得到需要的指定队列