iOS - GCD
概念
big
GCD (Grand Central Dispatch): 偉大的的中樞調節器; GCD 是蘋果公司開發的一個創建子線程的一個方法,主要為多核的并行開發運算提出解決方案,利用 GCD 可以是的 CPU 的內核運用的更加充分,同時 GCD 也會自動管理線程中的生命周期。
small
1.隊列:隊列是一種線性表,隊列的上下都是開口的與棧相反,隊列中遵循的原則是FIFO即為先進先出,隊列是在表尾進行添加操作,表頭進行刪除操作即先進先出后進后出。
2.同步:同步是指的是在調用方法時,按照順序的執行一些代碼。在第一個方法沒有執行完的時候第二個方法是不會進行的。
3.異步:異步和同步相反,調用方法時,當沒有收到第一個方法調用的返回值時,第二個方法也可以執行。
4.串行:程序運行時,程序會按照順序執行代碼,只是存在一個運行上下文。
5.并發:程序運行時,程序存在多個運行上下文,可以通過這些上下文執行不同的代碼。
多線程編程
我們研究多線程編程之前,需要知道當我們執行一段代碼的時候 CPU是如何執行的。
通過上圖我們要了解一個概念叫做上下文切換,上下文切換是 OSX 和 iOS 的核心 XNU 內核在發生操作系統事件時會切換執行路徑,例如 CPU的寄存器等信息保存到各自路徑專用的內存塊中,從切換目標路徑專用的內存塊中,復原 CPU 寄存器等信息,繼續執行切換路徑的 CPU 命令列。叫做上下文切換。
利用這種編程的方式叫做多線程編程,但是通過多線程編程同樣的也會產生很多的問題具體問題如下
- 多線程更新相同的資源會導致數據的不一致。
具體表現為兩個線程同時對一個數據進行更新,而兩個線程得到的數據不相同從而導致數據的不一致性。 - 死鎖問題,多個線程之間相互持有,造成持續等待。
如下代碼就發生了線程的死鎖,程序先執行 1,這時候同步線程會讓程序進入等待等 2 執行完了之后再執行 3,但是 sync 有產生了隊列,隊列會將操作放到對尾 2 等到 3 執行完了在執行,而 3 又在等待 2 執行完了它在執行。所以造成了死鎖現象。 - 過多的線程會消耗大量的內存。
因為過多的線程會使得 CPU 大量的調用“上下文切換”從而使得 CPU 的消耗巨大,從而導致程序的卡頓。
printf("1");
dispatch_sync(dispatch_get_main_queue(),^{
NSLog(@"線程發生了死鎖");
printf("2");
})
printf("3");
GCD Code
dispatch_queue_create,dispatch_sync 和 dispatch_async
通過 dispatch_queue_create 函數可以生成 Dispatch Queue 根據參數的不同可以生成 Serial 和 Concurrent 兩種類型的 Dispatch Queue。
dispatch_queue_t queue = dispatch_queue_create("www.kong.com",NULL);
..create后面的兩個參數:<br>第一個參數是隊列的名稱<br>第二個參數傳
NULL的時候是創建一個串行的隊列。傳
DISPATCH_QUEUE_CONCURRENT`的時候是創建一個并發隊列。
這樣我們分四種情況對 GCD 進行討論,同步串行,同步并發,異步串行,異步并發.
** 同步串行**
dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", NULL);
dispatch_sync(queue ,^{
NSLog(@"a當前的線程為 %@",[NSThread currentThread]);
});
dispatch_sync(queue ,^{
NSLog(@"b當前的線程為 %@",[NSThread currentThread]);
});
dispatch_sync(queue ,^{
NSLog(@"c當前的線程為 %@",[NSThread currentThread]);
});
dispatch_release(queue);//ARC 條件下我們需要將其進行釋放。
打印的結果為
a當前的線程為 <NSThread: 0x12f505470>{number = 1, name = main}
b當前的線程為 <NSThread: 0x12f505470>{number = 1, name = main}
c當前的線程為 <NSThread: 0x12f505470>{number = 1, name = main}
創建了串行隊列,通過同步的方式添加到Dispatch Queue 等待隊列中,沒有開辟新的線程,所有的打印都是在主線程中進行,隊列 queue 按照 abc 的順序添加到等待隊列中,也就是 FIFO 原則,出隊列的時候也是滿足該原則實現了先進先出,后進后出,打印的時候按順序進行打印。
** 同步并發**
dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue ,^{
NSLog(@"a當前的線程為 %@",[NSThread currentThread]);
});
dispatch_sync(queue ,^{
NSLog(@"b當前的線程為 %@",[NSThread currentThread]);
});
dispatch_sync(queue ,^{
NSLog(@"c當前的線程為 %@",[NSThread currentThread]);
});
dispatch_release(queue);//ARC 條件下我們需要將其進行釋放。
打印的結果為
a當前的線程為 <NSThread: 0x12f505470>{number = 1, name = main}
b當前的線程為 <NSThread: 0x12f505470>{number = 1, name = main}
c當前的線程為 <NSThread: 0x12f505470>{number = 1, name = main}
創建了并發的隊列添加到隊列中的時候用 sync
同步的方式將其追加到Dispatch Queue 中 沒有開辟新的線程,所有的數據處理仍然是在主線程中進行處理,Dispatch Queue 按照追加的順序(隊列 FIFO)的方式執行處理。
** 異步串行**
dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", NULL);
dispatch_async(queue ,^{
NSLog(@"a當前的線程為 %@",[NSThread currentThread]);
});
dispatch_async(queue ,^{
NSLog(@"b當前的線程為 %@",[NSThread currentThread]);
});
dispatch_async(queue ,^{
NSLog(@"c當前的線程為 %@",[NSThread currentThread]);
});
dispatch_release(queue);//ARC 條件下我們需要將其進行釋放。
打印的結果為
a當前的線程為 <NSThread: 0x12f505470>{number = 2, name = (null)} (這里的 name = null,可以通過[NSThread currentThread]來設置)
b當前的線程為 <NSThread: 0x12f505470>{number = 2, name = (null)}
c當前的線程為 <NSThread: 0x12f505470>{number = 2, name = (null)}
創建串行的Queue,然后通過異步的方式添加到隊列中去,使得 Dispatch Queue開辟了一個新的線程,但是還是通過同步隊列的方式來進行執行,都是在dispatch_async 創建出來的線程中按照順序執行隊列中的操作。
** 異步并發**
dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue ,^{
NSLog(@"a當前的線程為 %@",[NSThread currentThread]);
});
dispatch_async(queue ,^{
NSLog(@"b當前的線程為 %@",[NSThread currentThread]);
});
dispatch_async(queue ,^{
NSLog(@"c當前的線程為 %@",[NSThread currentThread]);
});
dispatch_release(queue);//ARC 條件下我們需要將其進行釋放。
打印的結果為:
a當前的線程為 <NSThread: 0x147d41a20>{number = 2, name = (null)}
c當前的線程為 <NSThread: 0x147d41a20>{number = 2, name = (null)}
b當前的線程為 <NSThread: 0x147e51ec0>{number = 3, name = (null)}
創建的是并發隊列,通過異步的方式將其追加到 Dispatch Queue 中,就會使得 CPU 開辟新的線程來執行代碼塊 Blcok 中的代碼。從而達到程序流暢的目的。
GCD中其他的一些方法
** 利用 global 創建線程**
//通過dispatch_get_global_queue 創建的是并發的隊列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
執行耗時操作
});
*對于 main dispatch_queue 和 global dispatch_queue 執行 dispatch_retain 函數和 dispatch_release 函數不會引起任何的變化,也不會有任何的問題。
當我們使用 dispatch_queue_create 的時候要考慮到在 ARC 中何時執行 dispatch_retain 和 dispatch_release 兩個函數方法。
** dispatch_after**
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"3秒后執行該方法");
});
通過這種方法延遲3秒執行,要知道這種方法的實現是在3秒后將 block 中的代碼追加到 Dispatch Queue 中,并不是在指定的時間后進行處理。不像 NSTimer 里面的設置在什么時間后進行處理。
** dispatch_group**
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"執行 block 1 %@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"執行 block 2 %@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"執行 block 3 %@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"執行 block 4 %@",[NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"執行完 group 執行的操作");
});
通過運用組的話,可以先執行組中的內容,然后當組中的內容執行完了之后用 group 的 notify 方法可以在執行 block 中的方法。這種方法用于順序執行,例如下載圖片的時候,下載多張圖片,然后進行拼接到一起,時候可以用這種方法將兩個圖片都下載下來,然后在 group_notify中對圖片進行拼接,然后得到結果。group_notify 也是起到一個追加的作用,將 block 中的內容追加到 Dispatch Queue 的后面。等到 group 中的隊列執行完后在執行。
** dispatch_barrier_async**
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"向數據庫中寫入數據");
});
dispatch_barrier_async(queue, ^{
NSLog(@"從數據庫中進行數據的讀取");
});
dispatch_async(queue, ^{
NSLog(@"從數據庫中寫入數據");
});
通過 dispatch_barrier_async 方法可以使當前的線程只執行 barrier_async(block)中的操作在兩次寫入操作的時候如果不用 dispatch_barrier_async 方法的話就會導致讀取數據的同時也在寫入數據,就導致了獲取的數據不準確,此時的 dispatch_barrier_async 就可以起到線程鎖的作用,保障讀寫的安全。
** dispatch_apply**
NSArray *applyArray = @[@1,@2,@3,@4,@5,@6];
dispatch_apply([applyArray count], queue, ^(size_t index) {
NSLog(@"%zu %@",index,[applyArray objectAtIndex:index]);
});
這段代碼是把一項任務提交到隊列中多次執行,隊列的串行并行由創建的隊列所決定。這里類似一個循環遍歷的功能,這樣將不想關的循環調到后臺線程執行,會將執行效率大大提高。
GCD 底層實現。
GCD 是用于管理追加 Block 的 C 語言層實現 FIFO隊列
里面主要運用了 libdispatch,Libc(pthreads),XNU內核。
GCD中的 API 全部為包含在 libdispatch 庫中的 C 語言函數,Dispatch Queue通過結構體和鏈表,被實現為 FIFO隊列 ,FIFO隊列管理是通過 dispatch_async 等函所追加的 Block。
Block 并不是直接加入 FIFO隊列中,而是先加入 Dispatch Continuation 這一個 dispatch_continuation_t 類型的結構體中,然后在加入 FIFO隊列,該 Dispatch Continuation 用于記憶 Block 所屬的 DispatchGroup 和其他的一些信息。 當 Dispatch Queue 中執行 Block 的時候,libdispatch 從 Global Dispatch Queue 自身的 FIFO 隊列中取出 Dispatch Continuation ,調用 pthread_workqueue_additem_up 函數。將該 Global Dispatch Queue 自身、符合其優先級的 workqueue 信息以及為執行 Dispatch Continuation 的回調函數等傳遞給參數。 workqueue 最終決定是否生成線程。
workqueue 的線程執行 pthread_workqueue 函數,該函數調用 libdispatch 的回調函數,在該回調函數中執行加入到 Dispatch Continuation 的 Block。
Block 執行結束后,進行通知 DispatchGroup 結束,釋放 Dispatch Continuation 等處理,開始準備執行加入到 Global Dispatch Queue中的下一個 Block。
說明:
本文參考
圖書《iOS與 OSX 多線程和內存管理》