死鎖一直都是在使用多線程時,需要注意的一個問題庆锦。以前對同步捅位、異步,串行搂抒、并行只有一個模糊的概念绿渣,想想也是時候整理一下了。再看看之前的博客燕耿,已經(jīng)很久沒有干貨了【說得好像之前有干貨一樣】中符,所以,這篇博客誉帅,我盡最大努力淀散,也借鑒了很多其他博客中的例子,來講解GCD死鎖問題蚜锨。
環(huán)境信息:
Mac OS X 10.10.5
Xcode 6.4
iOS? 8.4
正文
串行與并行
在使用GCD的時候档插,我們會把需要處理的任務(wù)放到Block中,然后將任務(wù)追加到相應(yīng)的隊(duì)列里面亚再,這個隊(duì)列郭膛,叫做Dispatch Queue。然而氛悬,存在于兩種Dispatch Queue则剃,一種是要等待上一個執(zhí)行完,再執(zhí)行下一個的Serial Dispatch Queue如捅,這叫做串行隊(duì)列棍现;另一種,則是不需要上一個執(zhí)行完镜遣,就能執(zhí)行下一個的Concurrent Dispatch Queue己肮,叫做并行隊(duì)列。這兩種,均遵循FIFO原則谎僻。
舉一個簡單的例子娄柳,在三個任務(wù)中輸出1、2艘绍、3赤拒,串行隊(duì)列輸出是有序的1、2鞍盗、3需了,但是并行隊(duì)列的先后順序就不一定了跳昼。
那么般甲,并行隊(duì)列又是怎么在執(zhí)行呢?
雖然可以同時多個任務(wù)的處理鹅颊,但是并行隊(duì)列的處理量敷存,還是要根據(jù)當(dāng)前系統(tǒng)狀態(tài)來。如果當(dāng)前系統(tǒng)狀態(tài)最多處理2個任務(wù)堪伍,那么1锚烦、2會排在前面,3什么時候操作帝雇,就看1或者2誰先完成涮俄,然后3接在后面。
串行和并行就簡單說到這里尸闸,關(guān)于它們的技術(shù)點(diǎn)其實(shí)還有很多彻亲,可以自行了解。
同步與異步
串行與并行針對的是隊(duì)列吮廉,而同步與異步苞尝,針對的則是線程。最大的區(qū)別在于宦芦,同步線程要阻塞當(dāng)前線程宙址,必須要等待同步線程中的任務(wù)執(zhí)行完,返回以后调卑,才能繼續(xù)執(zhí)行下一任務(wù)抡砂;而異步線程則是不用等待。
僅憑這幾句話還是很難理解恬涧,所以之后準(zhǔn)備了很多案例舀患,可以邊分析邊理解。
GCD API
GCD API很多聊浅,這里僅介紹本文用到的。
1. 系統(tǒng)標(biāo)準(zhǔn)提供的兩個隊(duì)列
// 全局隊(duì)列,也是一個并行隊(duì)列
dispatch_get_global_queue
// 主隊(duì)列低匙,在主線程中運(yùn)行旷痕,因?yàn)橹骶€程只有一個,所以這是一個串行隊(duì)列
dispatch_get_main_queue
2. 除此之外顽冶,還可以自己生成隊(duì)列
// 從DISPATCH_QUEUE_SERIAL看出欺抗,這是串行隊(duì)列
dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL)
// 同理,這是一個并行隊(duì)列
dispatch_queue_create("com.demo.concurrentQueue", DISPATCH_QUEUE_CONCURRENT)
接下來是同步與異步線程的創(chuàng)建:
dispatch_sync(..., ^(block)) // 同步線程
dispatch_async(..., ^(block)) // 異步線程
案例與分析
假設(shè)你已經(jīng)基本了解了上面提到的知識强重,接下來進(jìn)入案例講解階段绞呈。
案例一:
NSLog(@"1"); // 任務(wù)1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務(wù)2
});
NSLog(@"3"); // 任務(wù)3
結(jié)果,控制臺輸出:
1
分析:
dispatch_sync表示是一個同步線程间景;
dispatch_get_main_queue表示運(yùn)行在主線程中的主隊(duì)列佃声;
任務(wù)2是同步線程的任務(wù)。
首先執(zhí)行任務(wù)1倘要,這是肯定沒問題的圾亏,只是接下來,程序遇到了同步線程封拧,那么它會進(jìn)入等待志鹃,等待任務(wù)2執(zhí)行完,然后執(zhí)行任務(wù)3泽西。但這是隊(duì)列曹铃,有任務(wù)來,當(dāng)然會將任務(wù)加到隊(duì)尾捧杉,然后遵循FIFO原則執(zhí)行任務(wù)陕见。那么,現(xiàn)在任務(wù)2就會被加到最后糠溜,任務(wù)3排在了任務(wù)2前面淳玩,問題來了:
任務(wù)3要等任務(wù)2執(zhí)行完才能執(zhí)行,任務(wù)2由排在任務(wù)3后面非竿,意味著任務(wù)2要在任務(wù)3執(zhí)行完才能執(zhí)行蜕着,所以他們進(jìn)入了互相等待的局面『熘【既然這樣承匣,那干脆就卡在這里吧】這就是死鎖。
案例二:
NSLog(@"1"); // 任務(wù)1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任務(wù)2
});
NSLog(@"3"); // 任務(wù)3
結(jié)果锤悄,控制臺輸出:
1
2
3
分析:
首先執(zhí)行任務(wù)1韧骗,接下來會遇到一個同步線程,程序會進(jìn)入等待零聚。等待任務(wù)2執(zhí)行完成以后袍暴,才能繼續(xù)執(zhí)行任務(wù)3些侍。從dispatch_get_global_queue可以看出,任務(wù)2被加入到了全局的并行隊(duì)列中政模,當(dāng)并行隊(duì)列執(zhí)行完任務(wù)2以后岗宣,返回到主隊(duì)列,繼續(xù)執(zhí)行任務(wù)3淋样。
案例三:
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任務(wù)1
dispatch_async(queue, ^{
NSLog(@"2"); // 任務(wù)2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任務(wù)3
});
NSLog(@"4"); // 任務(wù)4
});
NSLog(@"5"); // 任務(wù)5
結(jié)果耗式,控制臺輸出:
1
5
2
// 5和2的順序不一定
分析:
這個案例沒有使用系統(tǒng)提供的串行或并行隊(duì)列,而是自己通過dispatch_queue_create函數(shù)創(chuàng)建了一個DISPATCH_QUEUE_SERIAL的串行隊(duì)列趁猴。
1.執(zhí)行任務(wù)1刊咳;
2.遇到異步線程,將【任務(wù)2儡司、同步線程娱挨、任務(wù)4】加入串行隊(duì)列中。因?yàn)槭钱惒骄€程枫慷,所以在主線程中的任務(wù)5不必等待異步線程中的所有任務(wù)完成让蕾;
3.因?yàn)槿蝿?wù)5不必等待浪规,所以2和5的輸出順序不能確定或听;
4.任務(wù)2執(zhí)行完以后,遇到同步線程笋婿,這時誉裆,將任務(wù)3加入串行隊(duì)列;
5.又因?yàn)槿蝿?wù)4比任務(wù)3早加入串行隊(duì)列缸濒,所以足丢,任務(wù)3要等待任務(wù)4完成以后,才能執(zhí)行庇配。但是任務(wù)3所在的同步線程會阻塞斩跌,所以任務(wù)4必須等任務(wù)3執(zhí)行完以后再執(zhí)行。這就又陷入了無限的等待中捞慌,造成死鎖耀鸦。
案例四:
NSLog(@"1"); // 任務(wù)1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2"); // 任務(wù)2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3"); // 任務(wù)3
});
NSLog(@"4"); // 任務(wù)4
});
NSLog(@"5"); // 任務(wù)5
結(jié)果,控制臺輸出:
1
2
5
3
4
// 5和2的順序不一定
分析:
首先啸澡,將【任務(wù)1袖订、異步線程、任務(wù)5】加入Main Queue中嗅虏,異步線程中的任務(wù)是:【任務(wù)2洛姑、同步線程、任務(wù)4】皮服。
所以楞艾,先執(zhí)行任務(wù)1参咙,然后將異步線程中的任務(wù)加入到Global Queue中,因?yàn)楫惒骄€程硫眯,所以任務(wù)5不用等待昂勒,結(jié)果就是2和5的輸出順序不一定。
然后再看異步線程中的任務(wù)執(zhí)行順序舟铜。任務(wù)2執(zhí)行完以后戈盈,遇到同步線程。將同步線程中的任務(wù)加入到Main Queue中谆刨,這時加入的任務(wù)3在任務(wù)5的后面塘娶。
當(dāng)任務(wù)3執(zhí)行完以后,沒有了阻塞痊夭,程序繼續(xù)執(zhí)行任務(wù)4刁岸。
從以上的分析來看,得到的幾個結(jié)果:1最先執(zhí)行她我;2和5順序不一定虹曙;4一定在3后面。
案例五:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1"); // 任務(wù)1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務(wù)2
});
NSLog(@"3"); // 任務(wù)3
});
NSLog(@"4"); // 任務(wù)4
while (1) {
}
NSLog(@"5"); // 任務(wù)5
結(jié)果番舆,控制臺輸出:
1
4
// 1和4的順序不一定
分析:
和上面幾個案例的分析類似酝碳,先來看看都有哪些任務(wù)加入了Main Queue:【異步線程、任務(wù)4恨狈、死循環(huán)疏哗、任務(wù)5】。
在加入到Global Queue異步線程中的任務(wù)有:【任務(wù)1禾怠、同步線程返奉、任務(wù)3】。
第一個就是異步線程吗氏,任務(wù)4不用等待芽偏,所以結(jié)果任務(wù)1和任務(wù)4順序不一定。
任務(wù)4完成后弦讽,程序進(jìn)入死循環(huán)污尉,Main Queue阻塞。但是加入到Global Queue的異步線程不受影響坦袍,繼續(xù)執(zhí)行任務(wù)1后面的同步線程十厢。
同步線程中,將任務(wù)2加入到了主線程捂齐,并且蛮放,任務(wù)3等待任務(wù)2完成以后才能執(zhí)行。這時的主線程奠宜,已經(jīng)被死循環(huán)阻塞了包颁。所以任務(wù)2無法執(zhí)行瞻想,當(dāng)然任務(wù)3也無法執(zhí)行,在死循環(huán)后的任務(wù)5也不會執(zhí)行娩嚼。
最終蘑险,只能得到1和4順序不定的結(jié)果。
參考
http://www.reibang.com/p/0b0d9b1f1f19
http://www.cnblogs.com/tangbinblog/p/4133481.html