一篇專題讓你秒懂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)。函數

對於部分新手來講, 可能認爲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 同步與異步

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

&3.3 GCD API

GCD API不少,這裏僅介紹本文用到的。 1. 系統標準提供的兩個隊列

// 全局隊列,一個特殊的並行隊列
dispatch_get_global_queue
// 主隊列,在主線程中運行,由於主線程只有一個,因此這是一個特殊的串行隊列
dispatch_get_main_queue

2. 除此以外,還能夠本身生成隊列

// 從DISPATCH_QUEUE_SERIAL看出,這是串行隊列
dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL)
// 同理,這是一個並行隊列
dispatch_queue_create("com.demo.concurrentQueue", DISPATCH_QUEUE_CONCURRENT)
 ```
3. 同步與異步線程的建立:

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分析:](http://upload-images.jianshu.io/upload_images/1483059-82244285841f9e60.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

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分析:](http://upload-images.jianshu.io/upload_images/1483059-08e2e54c5eb4dcdb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#### 案例三: 我們來點複雜一些的: 同步異步都有

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分析:](http://upload-images.jianshu.io/upload_images/1483059-6ed3ef17c4c48db1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

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 4 3 // 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分析:](http://upload-images.jianshu.io/upload_images/1483059-38da3d2425abecb7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#### 案例五: 當咱們典型案例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分析:](http://upload-images.jianshu.io/upload_images/1483059-9aa3a3ada8c09624.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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