一篇專題讓你秒懂GCD死鎖問題!

故事背景:

GCD的死鎖問題,一直是在使用多線程的時候,一個比較繞也必須要注意的問題,今天在工作中我們幾個同事又討論到了這個話題,通過和大伙的交流,發(fā)現(xiàn)不少的同事還是有繞不明白的地方, 我就想到,要不我來寫一個關于GCD死鎖的專題好了,于是就有了這篇關于GCD死鎖的專題: (下方的理解,僅限于我個人的理解,有不妥的地方,望大家斧正!)

如果您有任何意見或建議也可以通過郵件微博聯(lián)系我

環(huán)境信息:

Mac OS X 10.11.3
Xcode 7.2
iOS 9.2

闡述:

1. 什么是GCD ?

GCD帮碰,全稱 Grand Central Dispatch乖订「鞫郑可翻譯為”牛逼的中樞調度器”。它是純C語言的啃洋,提供了非常多強大的函數(shù)。
Grand是宏偉的篡悟、極重要的意思致板。

GCD是提供了功能強大的任務和隊列控制功能,相比于NSOperation更加底層坊夫,雖然現(xiàn)象蘋果極力的推薦使用NSOperation來解決多線程問題, 但是,就目前市場上大部分企業(yè)的iOS開發(fā)團隊而言, GCD仍然還是大頭, NSOperation也只會逐步的來替代GCD, 因此在開線程的時候,如果不注意也會導致一些問題, 比如死鎖砖第。

2.什么是GCD死鎖 ?

所謂死鎖,通常指有兩個線程A和B都卡住了环凿,A在等B ,B在等A,相互等待對方完成某些操作梧兼。A不能完成是因為它在等待B完成。但B也不能完成智听,因為它在等待A完成羽杰。于是大家都完不成,就導致了死鎖(DeadLock)到推。

對于部分新手來說, 可能認為GCD死鎖是很高端的操作系統(tǒng)層面的問題考赛,離我很遠,一般不會遇上莉测。其實這種想法是非常錯誤的颜骤,因為只要簡單三行代碼(如果愿意,甚至寫在一行就可以)就可以人為創(chuàng)造出死鎖的情況,如:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_sync(dispatch_get_main_queue(), ^(void){
            NSLog(@"這里死鎖了");
        });
    }
    return 0;
}
復制代碼

3. 要理解GCD死鎖,必須先理解GCD的幾個概念:

&3.1串行與并行

**串行和并行都是相對于隊列而言的 **
-隊列(負責調度任務)
-串行隊列:一個接一個的調度任務
-并發(fā)隊列:可以同時調度多個任務

在使用GCD的時候捣卤,我們會把需要處理的任務放到Block中忍抽,然后將任務追加到相應的隊列里面,這個隊列董朝,叫做Dispatch Queue梯找。
隊列一般存在于兩種Dispatch Queue
一種是要等待上一個執(zhí)行完益涧,再執(zhí)行下一個的Serial Dispatch Queue锈锤,這叫做串行隊列
另一種,則是不需要上一個執(zhí)行完久免,就能執(zhí)行下一個的Concurrent Dispatch Queue浅辙,叫做并行隊列
這兩種阎姥,均遵循FIFO原則,也就是先進先出原則记舆。

舉一個簡單的例子,在三個任務中輸出1呼巴、2泽腮、3,
串行隊列輸出是有序的1衣赶、2诊赊、3,
但是并行隊列的先后順序就不一定了府瞄。
復制代碼

那么碧磅,并行隊列又是怎么在執(zhí)行呢?
并行隊列雖然可以同時多個任務的處理遵馆,但是并行隊列的處理量鲸郊,還是要根據(jù)當前系統(tǒng)狀態(tài)來。如果當前系統(tǒng)狀態(tài)最多處理2個任務货邓,那么1秆撮、2會排在前面,3什么時候操作换况,就看1或者2誰先完成像吻,然后3接在后面。
串行和并行就簡單說到這里复隆,關于它們的技術點其實還有很多,可以自行了解姆涩。

&3.2 同步與異步

串行與并行針對的是隊列挽拂,而同步與異步,針對的則是線程骨饿。
最大的區(qū)別在于亏栈,同步線程要阻塞當前線程,必須要等待同步線程中的任務執(zhí)行完宏赘,返回以后绒北,才能繼續(xù)執(zhí)行下一任務;而異步線程則是不用等待察署。
僅憑這幾句話還是很難理解闷游,所以可以多準備幾個案例,邊分析邊理解。

&3.3 GCD API

GCD API很多脐往,這里僅介紹本文用到的休吠。

  1. 系統(tǒng)標準提供的兩個隊列
// 全局隊列,一個特殊的并行隊列
dispatch_get_global_queue
// 主隊列业簿,在主線程中運行瘤礁,因為主線程只有一個,所以這是一個特殊的串行隊列
dispatch_get_main_queue
復制代碼
  1. 除此之外梅尤,還可以自己生成隊列
// 從DISPATCH_QUEUE_SERIAL看出柜思,這是串行隊列
dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL)
// 同理,這是一個并行隊列
dispatch_queue_create("com.demo.concurrentQueue", DISPATCH_QUEUE_CONCURRENT)
復制代碼
  1. 同步與異步線程的創(chuàng)建:
dispatch_sync(..., ^(block)) // 同步線程
dispatch_async(..., ^(block)) // 異步線程
復制代碼

案例與分析

假設你已經(jīng)基本了解了上面提到的知識巷燥,接下來進入案例講解階段赡盘。

案例一: 當同步遇到了串行

NSLog(@"1"); // 任務1
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務3
復制代碼

控制臺輸出結果:

1
復制代碼

分析:

  • dispatch_sync表示是一個同步線程;
  • dispatch_get_main_queue表示運行在主線程中的主隊列矾湃;
  • 任務2是同步線程的任務亡脑。
  • 任務3需要等待任務2結束之后再執(zhí)行.

首先執(zhí)行任務1,這是肯定沒問題的邀跃,只是接下來霉咨,程序遇到了同步線程,那么它會進入等待拍屑,等待任務2執(zhí)行完途戒,然后執(zhí)行任務3。但這是主隊列僵驰,是一個特殊的串行隊列,有任務來喷斋,當然會將任務加到隊尾,然后遵循FIFO原則執(zhí)行任務蒜茴。那么星爪,現(xiàn)在任務2就會被加到最后,任務3排在了任務2前面粉私,問題來了:

任務3要等任務2執(zhí)行完才能執(zhí)行顽腾,任務2又排在任務3后面,意味著任務2要在任務3執(zhí)行完才能執(zhí)行诺核,所以他們進入了互相等待的局面抄肖。【既然這樣窖杀,那干脆就卡在這里吧】這就是死鎖漓摩。

[圖片上傳失敗...(image-220540-1566963152072)]

案例1分析:

案例二:當同步遇到了并行

NSLog(@"1"); // 任務1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務3
復制代碼

控制臺輸出結果為:

1
2
3
復制代碼

分析:
首先執(zhí)行任務1,接下來會遇到一個同步線程入客,程序會進入等待管毙。等待任務2執(zhí)行完成以后腿椎,才能繼續(xù)執(zhí)行任務3。從dispatch_get_global_queue可以看出锅风,任務2被加入到了全局的并行隊列中酥诽,當并行隊列執(zhí)行完任務2以后,返回到主隊列皱埠,繼續(xù)執(zhí)行任務3肮帐。

[圖片上傳失敗...(image-2cea82-1566963152072)]

案例2分析:

案例三: 咱們來點復雜一些的: 同步異步都有

dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任務1
dispatch_async(queue, ^{
    NSLog(@"2"); // 任務2
    dispatch_sync(queue, ^{  
        NSLog(@"3"); // 任務3
    });
    NSLog(@"4"); // 任務4
});
NSLog(@"5"); // 任務5
復制代碼

控制臺輸出結果:

1
5
2
// 2 和 5 的順序不一定 , 3, 4, 沒有輸出
復制代碼

分析:
這個案例沒有使用系統(tǒng)提供的串行或并行隊列,而是自己通過dispatch_queue_create函數(shù)創(chuàng)建了一個DISPATCH_QUEUE_SERIAL的串行隊列边器。
1.執(zhí)行任務1训枢;
2.遇到異步線程,將【任務2忘巧、同步線程恒界、任務4】加入串行隊列中。因為是異步線程砚嘴,所以在主線程中的任務5不必等待異步線程中的所有任務完成十酣;
3.因為任務5不必等待,所以2和5的輸出順序不能確定际长;
4.任務2執(zhí)行完以后耸采,遇到同步線程工育,這時虾宇,將任務3加入串行隊列;
5.又因為任務4比任務3早加入串行隊列如绸,所以,任務3要等待任務4完成以后搪泳,才能執(zhí)行岸军。但是任務3所在的同步線程會阻塞脏榆,所以任務4必須等任務3執(zhí)行完以后再執(zhí)行。這就又陷入了無限的等待中坞生,造成死鎖又兵。

[圖片上傳失敗...(image-792e13-1566963152072)]

案例3分析:

案例四:異步遇到同步回主線程

NSLog(@"1"); // 任務1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"2"); // 任務2
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"3"); // 任務3
    });
    NSLog(@"4"); // 任務4
});
NSLog(@"5"); // 任務5
復制代碼

控制臺輸出結果:

1
2
5
3
4
// 2 和 5 順序不一定
復制代碼

分析:
這個案例,我相信大家都熟悉,沒錯,這就是典型的異步加載數(shù)據(jù),回調主線程更新UI那個案例;

首先摔认,將【任務1、異步線程电谣、任務5】加入Main Queue中况鸣,異步線程中的任務是:【任務2镐捧、同步線程列牺、任務4】泌辫。

所以震放,先執(zhí)行任務1诈铛,然后將異步線程中的任務加入到Global Queue中幢竹,因為異步線程蜕企,所以任務5不用等待幸乒,結果就是2和5的輸出順序不一定腔召。

然后再看異步線程中的任務執(zhí)行順序。任務2執(zhí)行完以后等恐,遇到同步線程。將同步線程中的任務又回調加入到Main Queue中,這時加入的任務3在任務5的后面撤逢。

當任務3執(zhí)行完以后,沒有了阻塞,程序繼續(xù)執(zhí)行任務4议双。

從以上的分析來看,得到的幾個結果:1最先執(zhí)行;2和5順序不一定幼苛;4一定在3后面墙杯。

[圖片上傳失敗...(image-6bd8db-1566963152072)]

案例4分析:

案例五: 當我們典型案例4,遇到了主線程上出現(xiàn)無限循環(huán)的時候

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"1"); // 任務1
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2"); // 任務2
    });
    NSLog(@"3"); // 任務3
});
NSLog(@"4"); // 任務4
while (1) {
}
NSLog(@"5"); // 任務5
復制代碼

打印臺輸出結果:

1
4
// 1 和 4 順序不一定
復制代碼

分析:

和上面幾個案例的分析類似嫉髓,先來看看都有哪些任務加入了Main Queue:【異步線程算行、任務4、死循環(huán)硫狞、任務5】紧唱。

在加入到Global Queue異步線程中的任務有:【任務1、同步線程癣猾、任務3】五芝。

第一個就是異步線程隘擎,任務4不用等待,所以結果任務1和任務4順序不一定蹲姐。

任務4完成后扎阶,程序進入死循環(huán),Main Queue阻塞宰掉。但是加入到Global Queue的異步線程不受影響呵哨,繼續(xù)執(zhí)行任務1后面的同步線程。

同步線程中轨奄,將任務2加入到了主線程孟害,并且,任務3等待任務2完成以后才能執(zhí)行挪拟。這時的主線程挨务,已經(jīng)被死循環(huán)阻塞了。所以任務2無法執(zhí)行玉组,當然任務3也無法執(zhí)行谎柄,在死循環(huán)后的任務5也不會執(zhí)行。

最終惯雳,只能得到1和4順序不定的結果朝巫。

[圖片上傳失敗...(image-f24df9-1566963152072)]

案例5分析:

GCD死鎖問題,我們就暫時討論到這里,上面列舉的5各案例,基本已經(jīng)概括了GCD遇到的絕大多數(shù)情況了,以后有遇到其他情況的時候,再來補充,

鏈接:https://juejin.im/post/5d6542f76fb9a06b32608319
來源:掘金

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市石景,隨后出現(xiàn)的幾起案子捍歪,更是在濱河造成了極大的恐慌,老刑警劉巖鸵钝,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糙臼,死亡現(xiàn)場離奇詭異,居然都是意外死亡恩商,警方通過查閱死者的電腦和手機变逃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怠堪,“玉大人揽乱,你說我怎么就攤上這事∷诳螅” “怎么了凰棉?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長陌粹。 經(jīng)常有香客問我撒犀,道長,這世上最難降的妖魔是什么郑藏? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任入偷,我火速辦了婚禮,結果婚禮上焚志,老公的妹妹穿的比我還像新娘映凳。我一直安慰自己胆筒,他們只是感情好,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布诈豌。 她就那樣靜靜地躺著仆救,像睡著了一般。 火紅的嫁衣襯著肌膚如雪矫渔。 梳的紋絲不亂的頭發(fā)上彤蔽,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機與錄音蚌斩,去河邊找鬼。 笑死范嘱,一個胖子當著我的面吹牛送膳,可吹牛的內容都是我干的。 我是一名探鬼主播丑蛤,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼叠聋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了受裹?” 一聲冷哼從身側響起碌补,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棉饶,沒想到半個月后厦章,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡照藻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年袜啃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幸缕。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡群发,死狀恐怖,靈堂內的尸體忽然破棺而出发乔,到底是詐尸還是另有隱情熟妓,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布栏尚,位于F島的核電站起愈,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜告材,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一坤次、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斥赋,春花似錦缰猴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至隘膘,卻和暖如春疑故,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弯菊。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工纵势, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人管钳。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓钦铁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親才漆。 傳聞我的和親對象是個殘疾皇子牛曹,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內容