OC -> GCD

iOS - GCD

概念

big

GCD (Grand Central Dispatch): 偉大的的中樞調節器; GCD 是蘋果公司開發的一個創建子線程的一個方法,主要為多核的并行開發運算提出解決方案,利用 GCD 可以是的 CPU 的內核運用的更加充分,同時 GCD 也會自動管理線程中的生命周期。

small

1.隊列:隊列是一種線性表,隊列的上下都是開口的與棧相反,隊列中遵循的原則是FIFO即為先進先出,隊列是在表尾進行添加操作,表頭進行刪除操作即先進先出后進后出。

2.同步:同步是指的是在調用方法時,按照順序的執行一些代碼。在第一個方法沒有執行完的時候第二個方法是不會進行的。

3.異步:異步和同步相反,調用方法時,當沒有收到第一個方法調用的返回值時,第二個方法也可以執行。

4.串行:程序運行時,程序會按照順序執行代碼,只是存在一個運行上下文。

5.并發:程序運行時,程序存在多個運行上下文,可以通過這些上下文執行不同的代碼。

多線程編程

我們研究多線程編程之前,需要知道當我們執行一段代碼的時候 CPU是如何執行的。

Cup 執行過程



通過上圖我們要了解一個概念叫做上下文切換,上下文切換是 OSX 和 iOS 的核心 XNU 內核在發生操作系統事件時會切換執行路徑,例如 CPU的寄存器等信息保存到各自路徑專用的內存塊中,從切換目標路徑專用的內存塊中,復原 CPU 寄存器等信息,繼續執行切換路徑的 CPU 命令列。叫做上下文切換。

利用這種編程的方式叫做多線程編程,但是通過多線程編程同樣的也會產生很多的問題具體問題如下

  1. 多線程更新相同的資源會導致數據的不一致。

    具體表現為兩個線程同時對一個數據進行更新,而兩個線程得到的數據不相同從而導致數據的不一致性。
  2. 死鎖問題,多個線程之間相互持有,造成持續等待。

    如下代碼就發生了線程的死鎖,程序先執行 1,這時候同步線程會讓程序進入等待等 2 執行完了之后再執行 3,但是 sync 有產生了隊列,隊列會將操作放到對尾 2 等到 3 執行完了在執行,而 3 又在等待 2 執行完了它在執行。所以造成了死鎖現象。
  3. 過多的線程會消耗大量的內存。

    因為過多的線程會使得 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。

GCD 實現過程

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 多線程和內存管理》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 目錄(GCD): 關鍵詞 混淆點 場景應用 總結 1. 關鍵詞 線程概念: 獨立執行的代碼段,一個線程同時間只能執...
    Ryan___閱讀 1,296評論 0 3
  • 3.GCD GCD的全稱是Grand Central Dispatch,提供了非常多的純C語言的函數 GCD的優勢...
    Mario_ZJ閱讀 513評論 0 0
  • cp 命令 名稱: cp —— copy files and directories 摘要 cp [options...
    Manford閱讀 278評論 0 0
  • 分享人:蔡永堅 1. 問題描述:委托。 功能要求:已知有一個字符串數組,將該數組里的長度為2字符串選出來; 沒學習...
    胡諾閱讀 204評論 0 0
  • 這周作業是本月的最后一次作業了。也是我加入不寫就出局的第四個月,這個月有個好消息就是我加入的不講就出局第二期...
    dingding_74be閱讀 221評論 0 0