1.疑問
前幾天看到有人在問,如何判斷當(dāng)前執(zhí)行的隊(duì)列是不是指定隊(duì)列的問題扼脐。網(wǎng)上的資料有很多,但看完以后我反而整不明白了。判斷方法有3種:
- 使用dispatch_get_current_queue獲取當(dāng)前執(zhí)行的隊(duì)列褪测。
- 使用dispatch_queue_set_specific & dispatch_get_specific 標(biāo)記并獲取指定隊(duì)列
- 使用dispatch_queue_get_label 獲取隊(duì)列標(biāo)簽,比較字符串判斷潦刃。(SDWebImage中采用此法判斷)
其中侮措,dispatch_get_current_queue
在iOS6之后是被棄用的,蘋果只推薦在打印中使用乖杠,原因是它容易導(dǎo)致死鎖分扎。百度上大部分資料也沒有說清楚為什么會(huì)導(dǎo)致死鎖。(什么叫死鎖本篇文章不在詳細(xì)解釋胧洒,簡(jiǎn)單描述就是不同操作相互等待導(dǎo)致互相無法進(jìn)行)那么問題來了畏吓,看下面2個(gè)例子:
例子1: dispatch_get_current_queue在一般情況下是否發(fā)生死鎖
dispatch_queue_t queueA = dispatch_queue_create("com.lyk.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.lyk.queueB", NULL);
dispatch_sync(queueA /*(或者queueB)*/, ^{
/*function*/
dispatch_block_t block = ^{
NSLog(@"NO deadlock!");
};
if(dispatch_get_current_queue() == queueA) {
block();
}
else {
dispatch_sync(queueA, block);
}
});
將dispatch_sync閉包中執(zhí)行的內(nèi)容看作一個(gè)函數(shù),那么有2種執(zhí)行情況:
1.dispatch_sync(queueA ): dispatch_get_current_queue的返回值就是queueA卫漫,所以直接執(zhí)行block中的代碼塊菲饼,并不會(huì)發(fā)生死鎖。
2.dispatch_sync(queueB ): dispatch_get_current_queue的返回值是queueB列赎,此時(shí)會(huì)執(zhí)行else中的代碼宏悦,同步到queueA任務(wù)隊(duì)列中,執(zhí)行block代碼。這種情況兩個(gè)隊(duì)列之間各自執(zhí)行自己的任務(wù)饼煞,所以也不會(huì)發(fā)生死鎖辫塌。
例子2:dispatch_queue_set_specific & dispatch_get_specific處理一般情況下的隊(duì)列判斷
dispatch_queue_t queueA = dispatch_queue_create("com.lyk.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.lyk.queueB", NULL);
static int kQueueSpecific;
CFStringRef queueSpecificValue = CFSTR("queueA");
dispatch_queue_set_specific(queueA, &kQueueSpecific, (void *)queueSpecificValue, (dispatch_function_t)CFRelease);
dispatch_sync(queueA, ^{
dispatch_block_t block = ^{
NSLog(@"NO deadlock!");
};
CFStringRef retrievedValue = dispatch_get_specific(&kQueueSpecific);
if (retrievedValue) {
block();
} else {
dispatch_sync(queueA, block);
}
});
先簡(jiǎn)單介紹一下dispatch_queue_set_specific
& dispatch_get_specific
,這個(gè)函數(shù)通過給隊(duì)列設(shè)置標(biāo)記派哲,并且通過指定標(biāo)記來獲取當(dāng)前當(dāng)前隊(duì)列是不是指定的隊(duì)列臼氨。如果當(dāng)前隊(duì)列是,則會(huì)返回標(biāo)記字符串"queueA"芭届,如果不是則返回nil储矩。
同樣,也有2種情況:
1.dispatch_sync(queueA ): dispatch_get_specific的返回值是"queueA"褂乍,所以直接執(zhí)行block中的代碼塊持隧,并不會(huì)發(fā)生死鎖。
2.dispatch_sync(queueB ): dispatch_get_specific的返回值是nil逃片,此時(shí)會(huì)執(zhí)行else中的代碼屡拨,同步到queueA任務(wù)隊(duì)列中,同上褥实,也不會(huì)發(fā)生死鎖呀狼。
看完這2個(gè)例子,我發(fā)現(xiàn)2種方法都可以正確檢驗(yàn)隊(duì)列损离,并沒有發(fā)生傳說中的死鎖現(xiàn)象哥艇。那么為什么蘋果要廢棄
dispatch_get_current_queue
方法呢?
2.原因
When dispatch_get_current_queue() is called on the main thread, it may
or may not return the same value as dispatch_get_main_queue().
從蘋果API上對(duì)dispatch_get_current_queue
中的描述可以看到僻澎,這個(gè)函數(shù)的返回值不一定是用戶想要的那個(gè)返回值貌踏。但具體為什么,文檔上也沒有說清楚窟勃。
于是我們需要對(duì)GCD隊(duì)列進(jìn)行進(jìn)一步的研究祖乳,在為什么dispatch_get_current_queue被廢棄中找到了可以解釋這一切的答案。
原來秉氧,隊(duì)列之間也有指向關(guān)系眷昆,如圖:
可以看出,無論是串行還是并發(fā)隊(duì)列谬运,只要有targetq隙赁,都會(huì)一層一層地往上扔垦藏,直到線程池梆暖。所以無法單用某個(gè)隊(duì)列對(duì)象來描述“當(dāng)前隊(duì)列”這一概念的
而隊(duì)列的指向關(guān)系很可能被修改,而導(dǎo)致dispatch_get_current_queue
獲取的隊(duì)列和我們想要的不一致掂骏。當(dāng)設(shè)置了B的target queue為A轰驳,那么代碼中A B都可以看成是當(dāng)前隊(duì)列。
dispatch_set_target_queue(queueB, queueA);
dispatch_sync(queueB, ^{
dispatch_sync(queueA, ^{ /* deadlock! */ });
});
而使用dispatch_get_current_queue
獲取queueB就還是B,這種情況下就會(huì)出現(xiàn)死鎖级解。
dispatch_queue_t queueA = dispatch_queue_create("com.lyk.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.lyk.queueB", NULL);
//注意此處冒黑,將B的target queue設(shè)置為A
dispatch_set_target_queue(queueB, queueA);
dispatch_sync(queueB, ^{
dispatch_block_t block = ^{
NSLog(@"NO deadlock!");
};
if(dispatch_get_current_queue() == queueA) {
block();
}
else {
dispatch_sync(queueA, block);
}
});
此時(shí),dispatch_get_current_queue()的返回值是queueB勤哗,進(jìn)入else中抡爹,同步像queueA添加任務(wù),但這時(shí)候的dispatch_sync(queueB, ^{})相當(dāng)于已經(jīng)在queueA中添加任務(wù)芒划,就導(dǎo)致了同步死鎖冬竟。
3.解決
從上面我們知道了使用dispatch_get_current_queue()
在判斷隊(duì)列時(shí)候,可能因?yàn)殛?duì)列層級(jí)而導(dǎo)致同步死鎖民逼。那么正確的判斷方式則是使用dispatch_queue_set_specific
& dispatch_get_specific
泵殴,我們?cè)賮砜匆幌率褂盟O(shè)置了目標(biāo)隊(duì)列情況下的處理。
dispatch_queue_t queueA = dispatch_queue_create("com.lyk.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.lyk.queueB", NULL);
dispatch_set_target_queue(queueB, queueA);
static int kQueueSpecific;
CFStringRef queueSpecificValue = CFSTR("queueA");
dispatch_queue_set_specific(queueA, &kQueueSpecific, (void *)queueSpecificValue, (dispatch_function_t)CFRelease);
dispatch_sync(queueA(或者queueB), ^{
dispatch_block_t block = ^{
NSLog(@"NO deadlock!");
};
CFStringRef retrievedValue = dispatch_get_specific(&kQueueSpecific);
if (retrievedValue) {
block();
} else {
dispatch_sync(queueA, block);
}
});
情況依然是2種:
- dispatch_sync(queueA)時(shí),當(dāng)前隊(duì)列是queue, dispatch_get_specific將返回"queueA"拼苍,執(zhí)行block()不會(huì)死鎖笑诅。
- dispatch_sync(queueB)時(shí),queueB的目標(biāo)隊(duì)列是queueA,dispatch_get_specific將返回"queueA"疮鲫,依然執(zhí)行block()吆你,不會(huì)死鎖。
- 綜合之前的例子俊犯,再添加一個(gè)queueC早处,queueC不設(shè)置目標(biāo)隊(duì)列,dispatch_get_specific返回nil瘫析,會(huì)執(zhí)行
dispatch_sync(queueA, block)
,但是從queueC同步到queueA中砌梆,所以并不會(huì)導(dǎo)致死鎖。
4.總結(jié)
1.dispatch_get_current_queue()
可能會(huì)因?yàn)殛?duì)列層級(jí)關(guān)系導(dǎo)致死鎖贬循。不是用來判斷指定隊(duì)列的正確方式
2.dispatch_queue_set_specific
& dispatch_get_specific
不會(huì)因?yàn)殛?duì)列層級(jí)關(guān)系的變化而找到錯(cuò)誤的隊(duì)列咸包。
3.dispatch_queue_get_label
雖然在SDWebImage緩存模塊中使用來判斷隊(duì)列是不是ioQueue,但當(dāng)隊(duì)層級(jí)關(guān)系被改變時(shí)也會(huì)發(fā)生與dispatch_get_current_queue()
相同的問題杖虾。當(dāng)然sd中沒有指定特殊隊(duì)列關(guān)系的情況下不會(huì)出現(xiàn)問題烂瘫。大家可以試一下把下面代碼粘到剛剛的例子里去試一下。
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
NSLog(@"This method should be called from the ioQueue");
}
綜上所述奇适,判斷指定隊(duì)列正確的姿勢(shì)應(yīng)該是使用dispatch_queue_set_specific
& dispatch_get_specific
坟比。