每日一問28——GCD(判斷隊(duì)列正確姿勢(shì)與原因)

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)系眷昆,如圖:

955431-a3bcc180f2243ce0.png

可以看出,無論是串行還是并發(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坟比。

相關(guān)文章:

為什么dispatch_get_current_queue被廢棄

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嚷往,隨后出現(xiàn)的幾起案子葛账,更是在濱河造成了極大的恐慌,老刑警劉巖皮仁,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件籍琳,死亡現(xiàn)場(chǎng)離奇詭異菲宴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)趋急,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門喝峦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呜达,你說我怎么就攤上這事谣蠢。” “怎么了查近?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵漩怎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我嗦嗡,道長(zhǎng)勋锤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任侥祭,我火速辦了婚禮叁执,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矮冬。我一直安慰自己谈宛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布胎署。 她就那樣靜靜地躺著吆录,像睡著了一般。 火紅的嫁衣襯著肌膚如雪琼牧。 梳的紋絲不亂的頭發(fā)上恢筝,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音巨坊,去河邊找鬼撬槽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛趾撵,可吹牛的內(nèi)容都是我干的侄柔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼占调,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼暂题!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起究珊,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤薪者,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后苦银,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啸胧,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赶站,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年幔虏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纺念。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡想括,死狀恐怖陷谱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瑟蜈,我是刑警寧澤烟逊,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站铺根,受9級(jí)特大地震影響宪躯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜位迂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一访雪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掂林,春花似錦臣缀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锣杂,卻和暖如春脂倦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背元莫。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工狼讨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柒竞。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓政供,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親朽基。 傳聞我的和親對(duì)象是個(gè)殘疾皇子布隔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 本篇博客共分以下幾個(gè)模塊來介紹GCD的相關(guān)內(nèi)容: 多線程相關(guān)概念 多線程編程技術(shù)的優(yōu)缺點(diǎn)比較? GCD中的三種隊(duì)列...
    有夢(mèng)想的老伯伯閱讀 1,016評(píng)論 0 4
  • iOS中GCD的使用小結(jié) 作者dullgrass 2015.11.20 09:41*字?jǐn)?shù) 4996閱讀 20199...
    DanDanC閱讀 810評(píng)論 0 0
  • GCD筆記 總結(jié)一下多線程部分,最強(qiáng)大的無疑是GCD,那么先從這一塊部分講起. Dispatch Queue的種類...
    jins_1990閱讀 754評(píng)論 0 1
  • 一稼虎、GCD的API 1. Dispatch queue 在執(zhí)行處理時(shí)存在兩種Dispatch queue: 等待現(xiàn)...
    doudo閱讀 493評(píng)論 0 0
  • 很多人衅檀,變了就是變了,以前的我還會(huì)因此感到哀傷霎俩,現(xiàn)在的更是無力挽留哀军,有些人沉眶,有些事也是想挽留但終究留不住的。明知心...
    雯子mosquito閱讀 216評(píng)論 0 0