iOS多線程-GCD
GCD的簡介
GCD,全稱為 Grand Central Dispatch ,是iOS用來管理線程的技術。 純C語言,提供了非常多強大的函數。
GCD的優勢
- GCD會自動利用更多的CPU內核(比如雙核、四核)。
- GCD會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)。
- 程序員只需要告訴GCD想要執行什么任務,不需要編寫任何線程管理代碼。
GCD的中的概念
線程、任務和隊列的概念
概念 | 含義 |
---|---|
隊列(Dispatch Queue) | 裝載線程任務的隊形結構 |
并行隊列(Concurrent Dispatch Queue) | 并行隊列只能保證任務按照加入的順序開始執行,但是任務執行結束的時間無法保證 |
串行隊列(Serial Dispatch Queue) | 串行隊列一次只能執行一個任務 只能依次逐一先后有序的執行,等待上一個執行完,再執行下一個。 |
同步 | 不具備開啟新線程能力,任務創建后就要執行完畢才能繼續運行下一步 |
異步 | 具備開啟新線程能力,任務創建后可以先繞過,回頭再執行 |
創建隊列
//全局隊列,一個并行的隊列
dispatch_get_global_queue
//主隊列,主線程中的唯一隊列,一個串行隊列
dispatch_get_main_queue
自定義隊列
//串行隊列
dispatch_queue_create("serialqueue", DISPATCH_QUEUE_SERIAL)
//并行隊列
dispatch_queue_create("concurrentqueue", DISPATCH_QUEUE_CONCURRENT)
同步異步線程創建
//同步
dispatch_sync(..., ^(block))
//異步
dispatch_async(..., ^(block))
隊列和線程的區別
簡單來說,隊列就是用來存放任務的“暫存區”,而線程是執行任務的路徑,GCD將這些存在于隊列的任務取出來放到相應的線程上去執行,而隊列的性質決定了在其中的任務在哪種線程上執行
串行并行同步異步 - 簡單組合案例
使用GCD的核心問題
- 是否開啟新的線程
- 任務可以同時執行
- 結合以上兩個條件,也就等價“開啟新線程的能力 + 任務同步執行的權利”,只有在滿足能力與權利這兩個條件的前提下,我們才可以在同時執行多個任務。
再次進行多個實驗,其中要控制的變量有
- 同步或者異步
- 串行、并行、主隊列
- 外部任務和內部任務 所處隊列
**外部任務指的是出gcd派發的任務, 而內部任務是gcd派發的任務**
異步+串行
- (void)TestFunc
{
dispatch_queue_t squeue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"Begin----%@", [NSThread currentThread]);
dispatch_async(squeue, ^{
NSLog(@"Task 1---%@", [NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Task 2---%@", [NSThread currentThread]);
});
NSLog(@"End -----%@", [NSThread currentThread]);
}
// 輸出
Begin----<NSThread: 0x7fbdb5701c00>{number = 1, name = main}
End -----<NSThread: 0x7fbdb5701c00>{number = 1, name = main}
Task 2---<NSThread: 0x7fbdb5701c00>{number = 1, name = main}
Task 1---<NSThread: 0x7fbdb5615dc0>{number = 2, name = (null)}
結論分析
- 異步執行不會阻塞隊列,所以TestFunc會執行Begin后馬上執行End,TestFunc完成
- 異步+串行隊列可以開啟一個新線程 ,所以Task 1線程是2
- 異步+主隊列不開啟新線程,所以Task 2仍在線程1
異步+并行 :開啟新的線程,同時執行
dispatch_queue_t c_queue = dispatch_queue_create("標識符", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"Begin----%@", [NSThread currentThread]);
//使用異步函數封裝三個任務
dispatch_async(c_queue, ^{
NSLog(@"Task 1---%@", [NSThread currentThread]);
});
dispatch_async(c_queue, ^{
NSLog(@"test 2---%@", [NSThread currentThread]);
});
dispatch_async(c_queue, ^{
NSLog(@"Task 3---%@", [NSThread currentThread]);
});
NSLog(@"End -----%@", [NSThread currentThread]);
// 輸出
Begin----<NSThread: 0x7fc1fed03e20>{number = 1, name = main}
End -----<NSThread: 0x7fc1fed03e20>{number = 1, name = main}
Task 2---<NSThread: 0x7fc1fee09a80>{number = 3, name = (null)}
Task 1---<NSThread: 0x7fc1fec213d0>{number = 2, name = (null)}
Task 3---<NSThread: 0x7fc1fedb0830>{number = 4, name = (null)}
結論分析
- 兩者組合后的結果 開了三個新線程
- 函數在執行時,head和tail線,再回頭執行這三個任務
- 這三個任務是同時執行的,沒有先后,所以打印結果是“1-->3-->2”
同步+串行
// 情況 1: 內外分別屬于兩個不同的串行隊列
dispatch_queue_t s_queue = dispatch_queue_create("標識符", DISPATCH_QUEUE_SERIAL);
NSLog(@"Begin----%@", [NSThread currentThread]);
//使用異步函數封裝三個任務
dispatch_sync(s_queue, ^{
NSLog(@"Task 1---%@", [NSThread currentThread]);
});
dispatch_sync(s_queue, ^{
NSLog(@"Task 2---%@", [NSThread currentThread]);
});
NSLog(@"End -----%@", [NSThread currentThread]);
// 輸出1
Begin----<NSThread: 0x7fe5215079a0>{number = 1, name = main}
Task 1---<NSThread: 0x7fe5215079a0>{number = 1, name = main}
Task 2---<NSThread: 0x7fe5215079a0>{number = 1, name = main}
End -----<NSThread: 0x7fe5215079a0>{number = 1, name = main}
// 情況 2 內外分別屬于同一個的串行隊列 類似 /**同步+主隊列**/
dispatch_queue_t s_queue = dispatch_queue_create("標識符", DISPATCH_QUEUE_SERIAL);
NSLog(@"Begin----%@", [NSThread currentThread]);
dispatch_async(s_queue, ^{
NSLog(@"Begin 2---%@", [NSThread currentThread]);
dispatch_sync(s_queue, ^{
NSLog(@"Task 1---%@", [NSThread currentThread]);
});
dispatch_sync(s_queue, ^{
NSLog(@"Task 2---%@", [NSThread currentThread]);
});
NSLog(@"End 2-----%@", [NSThread currentThread]);
});
NSLog(@"End -----%@", [NSThread currentThread]);
// 輸出2
Begin----<NSThread: 0x7ff5be600da0>{number = 1, name = main}
End -----<NSThread: 0x7ff5be600da0>{number = 1, name = main}
Begin 2---<NSThread: 0x7ff5be70a400>{number = 2, name = (null)}
結論
- 當外部任務和同步執行的內部任務處于一個串行隊列時會導致死鎖
- 當外部任務和同步執行的內部任務分別處于兩個串行隊列是不會死鎖
- 雖然有兩個串行隊列,但是并沒有兩個線程
同步+并行
dispatch_queue_t c_queue = dispatch_queue_create("標識符", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"Begin----%@", [NSThread currentThread]);
//使用異步函數封裝三個任務
dispatch_sync(c_queue, ^{
NSLog(@"Task 1---%@", [NSThread currentThread]);
});
dispatch_sync(c_queue, ^{
NSLog(@"Task 2---%@", [NSThread currentThread]);
});
dispatch_sync(c_queue, ^{
NSLog(@"Task 3---%@", [NSThread currentThread]);
});
NSLog(@"End -----%@", [NSThread currentThread]);
結論分析
- 同步執行執行意味著
- 不能開啟新的線程
- 任務創建后必須執行完才能往下走
- 并行隊列意味著
- 任務之間不需要排隊,且具有同時被執行的“”
- 兩者組合后的結果
- 所有任務都只能在主線程中執行
- 函數在執行時,必須按照代碼的書寫順序一行一行地執行完才能繼續
- 注意事項
在這里即便是并行隊列,任務可以同時執行,但是由于只存在一個主線程,所以沒法把任務分發到不同的線程去同步處理,其結果就是只能在主線程里按順序挨個挨個執行了
異步+主隊列 :所有任務都可以先跳過,之后再來“按順序”執行
同步+主隊列 :死鎖
串行并行同步異步 - 復雜組合案例
聲明:本文非原創,僅僅整理一些開發技能知識文章,以作存檔學習用
參考
[1] http://www.lxweimin.com/p/414b8e91e021 // 這篇文章中有很多東西都無法解釋通順
[2] http://www.devhua.com/2016/01/25/iOS-GCD-deadlock/
[3] https://objccn.io/issue-2-1/
[4]http://www.lxweimin.com/p/bbabef8aa1fe // 死鎖的解釋和 1 不同,似乎是可以解釋通
[5]https://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1 // 有動圖?。?/p>
======
感慨: 之前在簡書中搜集了很多GCD和死鎖的文章,比如參考1中,認為死鎖是因為線程阻塞了,阻塞的任務被加入到串行隊列,并且位于下一個任務的后邊,形成環形等待,所以死鎖, 但是一直疑問,為什么阻塞了, 下一個任務還可以被加入隊列,解釋不通啊,
參考4 中認為死鎖是隊列阻塞而不是線程,內部任務即同步阻塞任務在隊列頭部,而外部任務正在執行,外部任務等待內部任務結束才可以結束, 而內部任務卻在等待執行, 說起來恨到看圖吧