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

故事背景:

GCD的死鎖問題,一直是在使用多線程的時候,一個比較繞也必需要注意的問題,今天在工做中咱們幾個同事又討論到了這個話題,經過和大夥的交流,發現很多的同事仍是有繞不明白的地方, 我就想到,要不我來寫一個關於GCD死鎖的專題好了,因而就有了這篇關於GCD死鎖的專題: (下方的理解,僅限於我我的的理解,有不妥的地方,望你們斧正!)多線程

若是您有任何意見或建議也能夠經過郵件微博聯繫我併發

環境信息:

Mac OS X 10.11.3
Xcode 7.2
iOS 9.2異步

闡述:

1. 什麼是GCD ?

GCD,全稱 Grand Central Dispatch。可翻譯爲」牛逼的中樞調度器」。它是純C語言的,提供了很是多強大的函數。
Grand是宏偉的、極重要的意思。async

GCD是提供了功能強大的任務和隊列控制功能,相比於NSOperation更加底層,雖然現象蘋果極力的推薦使用NSOperation來解決多線程問題, 可是,就目前市場上大部分企業的iOS開發團隊而言, GCD仍然仍是大頭, NSOperation也只會逐步的來替代GCD, 所以在開線程的時候,若是不注意也會致使一些問題, 好比死鎖。函數

2.什麼是GCD死鎖 ?

所謂死鎖,一般指有兩個線程A和B都卡住了,A在等B ,B在等A,相互等待對方完成某些操做。A不能完成是由於它在等待B完成。但B也不能完成,由於它在等待A完成。因而你們都完不成,就致使了死鎖(DeadLock)。ui

對於部分新手來講, 可能認爲GCD死鎖是很高端的操做系統層面的問題,離我很遠,通常不會趕上。其實這種想法是很是錯誤的,由於只要簡單三行代碼(若是願意,甚至寫在一行就能夠)就能夠人爲創造出死鎖的狀況,如:spa

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

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

&3.1串行與並行

**串行和並行都是相對於隊列而言的 **
-隊列(負責調度任務)
-串行隊列:一個接一個的調度任務
-併發隊列:能夠同時調度多個任務
操作系統

在使用GCD的時候,咱們會把須要處理的任務放到Block中,而後將任務追加到相應的隊列裏面,這個隊列,叫作Dispatch Queue
隊列通常存在於兩種Dispatch Queue
一種是要等待上一個執行完,再執行下一個的Serial Dispatch Queue,這叫作串行隊列
另外一種,則是不須要上一個執行完,就能執行下一個的Concurrent Dispatch Queue,叫作並行隊列
這兩種,均遵循FIFO原則,也就是先進先出原則。線程

舉一個簡單的例子,在三個任務中輸出一、二、3,
串行隊列輸出是有序的一、二、3,
可是並行隊列的前後順序就不必定了。
複製代碼

那麼,並行隊列又是怎麼在執行呢?
並行隊列雖然能夠同時多個任務的處理,可是並行隊列的處理量,仍是要根據當前系統狀態來。若是當前系統狀態最多處理2個任務,那麼一、2會排在前面,3何時操做,就看1或者2誰先完成,而後3接在後面。
串行和並行就簡單說到這裏,關於它們的技術點其實還有不少,能夠自行了解。翻譯

&3.2 同步與異步

串行與並行針對的是隊列,而同步與異步,針對的則是線程。
最大的區別在於,同步線程要阻塞當前線程,必需要等待同步線程中的任務執行完,返回之後,才能繼續執行下一任務;而異步線程則是不用等待。
僅憑這幾句話仍是很難理解,因此能夠多準備幾個案例,邊分析邊理解。

&3.3 GCD API

GCD API不少,這裏僅介紹本文用到的。

  1. 系統標準提供的兩個隊列
// 全局隊列,一個特殊的並行隊列
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. 同步與異步線程的建立:
dispatch_sync(..., ^(block)) // 同步線程
dispatch_async(..., ^(block)) // 異步線程
複製代碼

案例與分析

假設你已經基本瞭解了上面提到的知識,接下來進入案例講解階段。

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

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結束以後再執行.

首先執行任務1,這是確定沒問題的,只是接下來,程序遇到了同步線程,那麼它會進入等待,等待任務2執行完,而後執行任務3。但這是主隊列,是一個特殊的串行隊列,有任務來,固然會將任務加到隊尾,而後遵循FIFO原則執行任務。那麼,如今任務2就會被加到最後,任務3排在了任務2前面,問題來了:

任務3要等任務2執行完才能執行,任務2又排在任務3後面,意味着任務2要在任務3執行完才能執行,因此他們進入了互相等待的局面。【既然這樣,那乾脆就卡在這裏吧】這就是死鎖。

案例1分析:

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

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

控制檯輸出結果爲:

1
2
3
複製代碼

分析:
首先執行任務1,接下來會遇到一個同步線程,程序會進入等待。等待任務2執行完成之後,才能繼續執行任務3。從dispatch_get_global_queue能夠看出,任務2被加入到了全局的並行隊列中,當並行隊列執行完任務2之後,返回到主隊列,繼續執行任務3。

案例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, 沒有輸出
複製代碼

分析:
這個案例沒有使用系統提供的串行或並行隊列,而是本身經過dispatch_queue_create函數建立了一個DISPATCH_QUEUE_SERIAL的串行隊列。
1.執行任務1;
2.遇到異步線程,將【任務二、同步線程、任務4】加入串行隊列中。由於是異步線程,因此在主線程中的任務5沒必要等待異步線程中的全部任務完成;
3.由於任務5沒必要等待,因此2和5的輸出順序不能肯定;
4.任務2執行完之後,遇到同步線程,這時,將任務3加入串行隊列;
5.又由於任務4比任務3早加入串行隊列,因此,任務3要等待任務4完成之後,才能執行。可是任務3所在的同步線程會阻塞,因此任務4必須等任務3執行完之後再執行。這就又陷入了無限的等待中,形成死鎖。

案例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 順序不必定
複製代碼

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

首先,將【任務一、異步線程、任務5】加入Main Queue中,異步線程中的任務是:【任務二、同步線程、任務4】。

因此,先執行任務1,而後將異步線程中的任務加入到Global Queue中,由於異步線程,因此任務5不用等待,結果就是2和5的輸出順序不必定。

而後再看異步線程中的任務執行順序。任務2執行完之後,遇到同步線程。將同步線程中的任務又回調加入到Main Queue中,這時加入的任務3在任務5的後面。

當任務3執行完之後,沒有了阻塞,程序繼續執行任務4。

從以上的分析來看,獲得的幾個結果:1最早執行;2和5順序不必定;4必定在3後面。

案例4分析:

案例五: 當咱們典型案例4,遇到了主線程上出現無限循環的時候

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:【異步線程、任務四、死循環、任務5】。

在加入到Global Queue異步線程中的任務有:【任務一、同步線程、任務3】。

第一個就是異步線程,任務4不用等待,因此結果任務1和任務4順序不必定。

任務4完成後,程序進入死循環,Main Queue阻塞。可是加入到Global Queue的異步線程不受影響,繼續執行任務1後面的同步線程。

同步線程中,將任務2加入到了主線程,而且,任務3等待任務2完成之後才能執行。這時的主線程,已經被死循環阻塞了。因此任務2沒法執行,固然任務3也沒法執行,在死循環後的任務5也不會執行。

最終,只能獲得1和4順序不定的結果。

案例5分析:

GCD死鎖問題,咱們就暫時討論到這裏,上面列舉的5各案例,基本已經歸納了GCD遇到的絕大多數狀況了,之後有遇到其餘狀況的時候,再來補充,


轉載於做者:DXSmile

連接:https://www.jianshu.com/p/201ccb40a3f8

相關文章
相關標籤/搜索