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; }
**串行和並行都是相對於隊列而言的 ** -隊列(負責調度任務) -串行隊列:一個接一個的調度任務 -併發隊列:能夠同時調度多個任務操作系統
在使用GCD的時候,咱們會把須要處理的任務放到Block中,而後將任務追加到相應的隊列裏面,這個隊列,叫作Dispatch Queue。 隊列通常存在於兩種Dispatch Queue, 一種是要等待上一個執行完,再執行下一個的Serial Dispatch Queue,這叫作串行隊列; 另外一種,則是不須要上一個執行完,就能執行下一個的Concurrent Dispatch Queue,叫作並行隊列。 這兩種,均遵循FIFO原則,也就是先進先出原則。線程
舉一個簡單的例子,在三個任務中輸出一、二、3, 串行隊列輸出是有序的一、二、3, 可是並行隊列的前後順序就不必定了。
那麼,並行隊列又是怎麼在執行呢? 並行隊列雖然能夠同時多個任務的處理,可是並行隊列的處理量,仍是要根據當前系統狀態來。若是當前系統狀態最多處理2個任務,那麼一、2會排在前面,3何時操做,就看1或者2誰先完成,而後3接在後面。 串行和並行就簡單說到這裏,關於它們的技術點其實還有不少,能夠自行了解。翻譯
串行與並行針對的是隊列,而同步與異步,針對的則是線程。 最大的區別在於,同步線程要阻塞當前線程,必需要等待同步線程中的任務執行完,返回之後,才能繼續執行下一任務;而異步線程則是不用等待。 僅憑這幾句話仍是很難理解,因此能夠多準備幾個案例,邊分析邊理解。3d
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遇到的絕大多數狀況了,之後有遇到其餘狀況的時候,再來補充,**