一热康、前言
根據(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é)
- GCD隊列是按照層級結(jié)構(gòu)來組織的膝舅,無法單用某個隊列對象來描述“當前隊列”
- dispatch_get_current_queue函數(shù)可能返回與預期不一致的結(jié)果
- 誤用dispatch_get_current_queue可能導致死鎖
- 設(shè)置隊列specific可以把任意數(shù)據(jù)以鍵值對的形式關(guān)聯(lián)到隊列里,從而得到需要的指定隊列