iOS------GCD一些概念的整理
在iOS開發(fā)中,經(jīng)常會使用到GCD多線程編程的技術(shù),在這里我們不多介紹GCD的具體使用,只是簡單的聊一下GCD相關(guān)的一些非主流問題。
首先我們從一個談到爛的話題開始:
死鎖
什么是死鎖呢?
所謂死鎖:是指兩個或兩個以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞現(xiàn)象,若無外力作用,他們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。
這是百度對死鎖的解釋。上面說的是進(jìn)程,當(dāng)然對于線程來說,同樣適用。
<strong>但是我想說的是GCD中出現(xiàn)了死鎖,不同于上述概念的死鎖</strong>,我們來看一下GCD中死鎖的形成:
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_sync(main_queue, ^{
NSLog(@"主隊列同步執(zhí)行");
});
如果項目中出現(xiàn)這樣的代碼,肯定會出現(xiàn)死鎖現(xiàn)象。上述代碼的意思是:將任務(wù)同步提交至主隊列執(zhí)行,主隊列需要停下目前正在執(zhí)行的任務(wù),轉(zhuǎn)而執(zhí)行同步提交的任務(wù)。但是因為主隊列是一個串行隊列,同時執(zhí)行其他任務(wù)。其實除了主隊了,任何串行隊列都不能同步執(zhí)行其他任務(wù):
dispatch_queue_t queue = dispatch_queue_create("ljt", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"在queue隊列同步執(zhí)行");
});
});
我們新創(chuàng)建一個串行隊列,然后將串行隊列異步提交至主隊列,然后在新創(chuàng)建的隊列中,同步提交任務(wù),這個時候還是會發(fā)生死鎖。
<strong>我們可以得出一個結(jié)論,使用dispatch_sync函數(shù)同步提交任務(wù)時,如果這句的上下文和要提交的目標(biāo)隊列是同一個隊列并且是串行隊列的話,肯定會死鎖的。而這種死鎖是一種線程調(diào)度死鎖</strong> 注意隊列是串行隊列,如果是并行隊列的話,是沒有問題的。
</br>具體的原因我們稍后分析,現(xiàn)在先看一看另一個問題:
隊列 線程 RunLoop的關(guān)系
隊列: 隊列其實是一個任務(wù)調(diào)度系統(tǒng),負(fù)責(zé)調(diào)度一個個線程執(zhí)行具體的任務(wù),可以把它理解為線程的容器,隊列分為串行隊列和并行隊列,串行隊列同時只能調(diào)度一個唯一的線程,而并行隊列同時可以調(diào)度多個線程。 線程 + 任務(wù)的容器 = 隊列
線程: 具體執(zhí)行任務(wù)的過程,
RunLoop: 每一個線程都有一個RunLoop,關(guān)于RunLoop的具體介紹請自行谷歌百度,其實RunLoop就是一個線程的?;顧C(jī)制,先需要的時候調(diào)起線程(無需重新創(chuàng)建),不需要的時候使線程休眠,以節(jié)省資源。
綜上:GCD是一個任務(wù)調(diào)度系統(tǒng),負(fù)責(zé)將我們提交的任務(wù)分配到不同的線程上去執(zhí)行,我們提交任務(wù)的方式有同步和異步。
同步提交:就是停下目前正在執(zhí)行的任務(wù),等待所提交的任務(wù)執(zhí)行完畢后再繼續(xù)執(zhí)行,會阻塞當(dāng)前任務(wù)的執(zhí)行
異步提交:只管向隊列中提交任務(wù),不用等待所提交的任務(wù)執(zhí)行完畢,所提交的任務(wù)具體什么時候執(zhí)行,這個要看當(dāng)前的系統(tǒng)的隊列狀態(tài)。不會阻塞當(dāng)前任務(wù)的執(zhí)行。
<strong>注意等待這個詞</strong>
而GCD得隊列又分為串行隊列和并行隊列,所以我們就可以得到四種不同的任務(wù)執(zhí)行方式:
串行同步提交:
串行異步提交:
并行同步提交:
并行異步提交:
現(xiàn)在我們就來分析一下最開始所出現(xiàn)的死鎖現(xiàn)象,我們知道,死鎖狀態(tài)是發(fā)生在第一種任務(wù)執(zhí)行方式中:
<strong>串行同步提交</strong> ,為什么會出現(xiàn)這個問題呢?
舉個例子: 這個例子也許不太合適,但是實在找不出更加合適的例子了。。
我們把隊列看做是廚師長,具體執(zhí)行的任務(wù)的線程可以看做是一個個的小廚,
串行隊列呢,廚師長同一時刻只允許調(diào)度一個小廚來工作,而這個小廚不能隨時切換當(dāng)前所處理任務(wù)(因為串行嘛),當(dāng)我們同步提交任務(wù)的時候,根據(jù)定義,我們需要停下當(dāng)前的任務(wù),立即執(zhí)行我們所提交的任務(wù),等待執(zhí)行完所提交的任務(wù)后繼續(xù)執(zhí)行之前暫停的任務(wù)。但是串行隊列(廚師長)同一時刻只有一個小廚可供調(diào)用,當(dāng)同步提交任務(wù)的時候,就相當(dāng)于說,小廚你不要放下現(xiàn)在手頭的工作,同時要把新分配的任務(wù)執(zhí)行完,這個時候小廚就為難了,我分身乏術(shù)啊,臣妾做不到啊。這就形成了隊列調(diào)度資源的競爭,出現(xiàn)了死鎖現(xiàn)象。
其他的幾種情況為什么沒有問題呢?
<strong>串行異步提交</strong> 異步,就是說,不用等待當(dāng)前任務(wù)執(zhí)行完畢。用上面的例子解釋:雖然廚師長,同一時刻只有一個小廚可供調(diào)用,但是因為是異步提交,當(dāng)接到任務(wù)交給小廚后,說這個任務(wù)需要執(zhí)行(不必同時立馬),小廚可以等到手頭的工作做完之后再來執(zhí)行,完全沒問題。這個時候其實只有一個小廚在工作,也就是實際上只有一個線程執(zhí)行</br>
<strong>并行同步提交</strong> 并行隊列,這個就更沒問題了,并行隊列相當(dāng)于說,廚師長可以有很多小廚可供調(diào)用,具體有多少小廚不太清楚,但是肯定夠用,并且小廚可以隨時切換當(dāng)前的所正在執(zhí)行的任務(wù)(因為并行嘛),當(dāng)同步提交的時候,廚師長要求這個小廚立馬切換工作狀態(tài),去執(zhí)行新提交分配的任務(wù),執(zhí)行完之后在回頭執(zhí)行之前的任務(wù),這個時候其實只有一個小廚在工作,也就是實際上只有一個線程執(zhí)行</br>
<strong>并行異步提交</strong> 這種情況下,就是一個廚師長可以調(diào)度多個小廚,廚師長只管把任務(wù)分配給不同的小廚,不用等待提交的任務(wù)立即執(zhí)行完成,小廚具體什么時候做完,廚師長不關(guān)心。
何時創(chuàng)建新的線程
1、隊列剛啟動的時候
當(dāng)我們創(chuàng)建隊列的時候,這個隊列是空的,當(dāng)我們向隊列中提交任務(wù)的時候才會創(chuàng)建新的線程。
2、串行隊列異步執(zhí)行
dispatch_queue_t queue = dispatch_queue_create("ljt", NULL);
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"異步提交任務(wù)1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"異步提交任務(wù)2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"異步提交任務(wù)3---%@",[NSThread currentThread]);
});
NSLog(@"隊列執(zhí)行完畢");
});
執(zhí)行結(jié)果:
2017-05-02 13:22:34.539 TestGCD[1490:58336] <NSThread: 0x600000071e40>{number = 1, name = main}
2017-05-02 13:22:34.539 TestGCD[1490:58336] 隊列執(zhí)行完畢
2017-05-02 13:22:37.585 TestGCD[1490:58381] 異步提交任務(wù)1---<NSThread: 0x608000074340>{number = 3, name = (null)}
2017-05-02 13:22:39.586 TestGCD[1490:58381] 異步提交任務(wù)2---<NSThread: 0x608000074340>{number = 3, name = (null)}
2017-05-02 13:22:40.653 TestGCD[1490:58381] 異步提交任務(wù)3---<NSThread: 0x608000074340>{number = 3, name = (null)}
由輸出的日志我們可以看出,為了方便對比,我們?yōu)槊總€任務(wù)設(shè)置了休眠時間,當(dāng)串行隊列異步提交的時候,會創(chuàng)建一個新的線程,任務(wù)1在等待3秒之后執(zhí)行,任務(wù)2需要等待任務(wù)執(zhí)行完畢2秒之后才能執(zhí)行,任務(wù)3需要等待任務(wù)2執(zhí)行完畢1秒之后才能執(zhí)行。我們可以發(fā)現(xiàn)串行執(zhí)行所提交的任務(wù),這任務(wù)在新的線程中還是一個一個執(zhí)行的,
3、并行隊列異步執(zhí)行
dispatch_queue_t queue = dispatch_queue_create("ljt", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"異步提交任務(wù)1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"異步提交任務(wù)2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"異步提交任務(wù)3---%@",[NSThread currentThread]);
});
NSLog(@"隊列執(zhí)行完畢");
});
執(zhí)行結(jié)果:
2017-05-02 13:25:03.881 TestGCD[1556:60317] <NSThread: 0x600000075a40>{number = 1, name = main}
2017-05-02 13:25:03.881 TestGCD[1556:60317] 隊列執(zhí)行完畢
2017-05-02 13:25:04.947 TestGCD[1556:60373] 異步提交任務(wù)3---<NSThread: 0x600000079740>{number = 3, name = (null)}
2017-05-02 13:25:05.905 TestGCD[1556:60371] 異步提交任務(wù)2---<NSThread: 0x60800007cac0>{number = 4, name = (null)}
2017-05-02 13:25:06.911 TestGCD[1556:60370] 異步提交任務(wù)1---<NSThread: 0x600000076380>{number = 5, name = (null)}
這個情況和第二種情況相比,只是隊列發(fā)生了變化,第二種情況下隊列是串行隊列,而現(xiàn)在是并發(fā)隊列,由日志輸出我們可以發(fā)現(xiàn),并發(fā)隊列中的任務(wù)是并發(fā)執(zhí)行的,沒有固定的順序。
所以我們可以總結(jié)如下:
| 同步or異步 | 并行隊列 | 手動創(chuàng)建的串行隊列 | 主隊列 |
|---|---|---|---|
| 同步 | 沒有開啟新的線程</br>串行執(zhí)行任務(wù) | 沒有開啟新的線程</br>串行執(zhí)行任務(wù) | 沒有開啟新的線程</br>串行執(zhí)行任務(wù)</br>(注意死鎖) |
| 異步 | 有開啟新的線程</br>并行執(zhí)行任務(wù) | 有開啟新的線程</br>串行執(zhí)行任務(wù) | 沒有開啟新的線程</br>串行執(zhí)行任務(wù) |
對一個并行隊列做同步操作就如同對一個串行隊列做異步操作
區(qū)別:對一個并行隊列做同步操作,隊列不會開啟新的線程,而對一個串行隊列做異步操作會開啟新的線程。
<strong>對一個并行隊列做同步操作:</strong>
dispatch_queue_t queue = dispatch_queue_create("ljt", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"隊列線程---%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
sleep(3);
NSLog(@"同步提交任務(wù)1---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
sleep(2);
NSLog(@"同步提交任務(wù)2---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
sleep(1);
NSLog(@"同步提交任務(wù)3---%@",[NSThread currentThread]);
});
NSLog(@"隊列執(zhí)行完畢");
});
輸出:
2017-05-02 13:52:07.246 TestGCD[1821:71817] <NSThread: 0x608000076d80>{number = 1, name = main}
2017-05-02 13:52:10.311 TestGCD[1821:71817] 同步提交任務(wù)1---<NSThread: 0x608000076d80>{number = 1, name = main}
2017-05-02 13:52:12.334 TestGCD[1821:71817] 同步提交任務(wù)2---<NSThread: 0x608000076d80>{number = 1, name = main}
2017-05-02 13:52:13.334 TestGCD[1821:71817] 同步提交任務(wù)3---<NSThread: 0x608000076d80>{number = 1, name = main}
2017-05-02 13:52:13.335 TestGCD[1821:71817] 隊列執(zhí)行完畢
任務(wù)1,2,3在同一個線程上執(zhí)行,和最初的隊列線程一致
<strong>對一個串行隊列做異步操作:</strong>
dispatch_queue_t queue = dispatch_queue_create("ljt", NULL);
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"異步提交任務(wù)1---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"異步提交任務(wù)2---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"異步提交任務(wù)3---%@",[NSThread currentThread]);
});
NSLog(@"隊列執(zhí)行完畢");
});
輸出:
2017-05-02 13:53:37.441 TestGCD[1842:72770] <NSThread: 0x60800007c080>{number = 1, name = main}
2017-05-02 13:53:37.442 TestGCD[1842:72770] 隊列執(zhí)行完畢
2017-05-02 13:53:40.502 TestGCD[1842:72805] 異步提交任務(wù)1---<NSThread: 0x60800007f200>{number = 3, name = (null)}
2017-05-02 13:53:42.560 TestGCD[1842:72805] 異步提交任務(wù)2---<NSThread: 0x60800007f200>{number = 3, name = (null)}
2017-05-02 13:53:43.621 TestGCD[1842:72805] 異步提交任務(wù)3---<NSThread: 0x60800007f200>{number = 3, name = (null)}
由日志我們可以看到:
任務(wù)1,2,3在同一個線程中執(zhí)行,和最初的隊列線程已經(jīng)不一致了
串行隊列不一定會在同一個線程中執(zhí)行
我們知道線程是有生命周期的,當(dāng)我們創(chuàng)建一個線程的時候,這個線程的RunLoop默認(rèn)是沒有開啟的,所以當(dāng)我們提交的一個任務(wù)執(zhí)行完畢之后,這個線程的使命的就完成,就會被系統(tǒng)銷毀,當(dāng)我們在此提交的時候,隊列會在線程池中再次拿出一個線程來執(zhí)行我們所提交的任務(wù)。這種情況在什么情況下出現(xiàn)呢?
我們考慮這樣一種情況,首先我們創(chuàng)建一個串行隊列,向隊列中提交一個任務(wù),然后我們30秒之后再向隊列中追加一個任務(wù),這個時候新追加的任務(wù)的執(zhí)行線程和最初的的任務(wù)線程很有可能不是同一個。
我們來看一下代碼:
dispatch_queue_t queue = dispatch_queue_create("ljt", NULL);
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"異步提交任務(wù)---%@",[NSThread currentThread]);
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)), queue, ^{
NSLog(@"延遲30秒異步提交任務(wù)---%@",[NSThread currentThread]);
});
NSLog(@"隊列執(zhí)行完畢");
});
輸出:
2017-05-02 14:02:18.584 TestGCD[1870:75267] <NSThread: 0x60800007cc40>{number = 1, name = main}
2017-05-02 14:02:18.585 TestGCD[1870:75267] 隊列執(zhí)行完畢
2017-05-02 14:02:18.585 TestGCD[1870:75300] 異步提交任務(wù)---<NSThread: 0x608000269100>{number = 3, name = (null)}
2017-05-02 14:02:48.585 TestGCD[1870:75302] 延遲30秒異步提交任務(wù)---<NSThread: 0x60800007cec0>{number = 4, name = (null)}
我們可以看到:
延遲30秒追加的任務(wù)和第一次提交的任務(wù)所執(zhí)行的線程不是同一個線程。