GCD之佇列的實現和使用

Devhl發表於2021-02-03

一、什麼是GCD?

以下是摘自蘋果的官方說明。

Grand Central Dispatch(GCD)是非同步執行任務的技術之一。一般將應用程式中記述的執行緒管理用的程式碼在系統級中實現。開發者只需要定義想執行的任務並追加到適當的Dispatch Queue中,GCD就能生成必要的執行緒並計劃執行任務。

 

二、GCD實現之Dispatch Queue

  • 用於管理追加的Block的C語言層實現的FIFO佇列
  • Atomic函式中實現的用於排他控制的輕量級訊號
  • 用於管理執行緒的C語言層實現的一些容器

GCD的API全部包含在libdispatch庫中的C語言函式。Dispatch Queue通過結構體和連結串列,被實現為FIFO佇列。FIFO佇列管理是通過dispatch_async等函式所追加的Block。

Block並不是直接加入FIFO佇列,而是先加入Dispatch Continuation這一dispatch_continution_t類結構體中,然後再加入FIFO佇列。該Dispatch Continuation用於記憶Block所屬的Dispatch Group和其他一些資訊,相當於一般常說的執行上下文。

Dispatch Queue可通過dispatch_set_target_queue函式設定,可以設定執行該Dispatch Queue處理的Dispatch Queue為目標。該目標可像串珠子一樣,設定多個連線在一起的Dispatch Queue。但是在連線串的最後必須設定為Main Dispatch Queue,或各種優先順序的Global Dispatch Queue,或是準備用於Serial Dispatch Queue的各種優先順序的 Global Dispatch Queue.

Global Dispatch Queue有如下8種。

  • Global Dispatch Queue (High Priority)
  • Global Dispatch Queue (Default Priority)
  • Global Dispatch Queue (Low Priority)
  • Global Dispatch Queue (Background Priority)
  • Global Dispatch Queue (High Overcommit Priority)
  • Global Dispatch Queue (Default Overcommit Priority)
  • Global Dispatch Queue (Low Overcommit Priority)
  • Global Dispatch Queue (Background Overcommit Priority)

優先順序中附有Overcommit的Global Dispatch Queue使用在Serial Dispatch Queue中。如Overcommit這個名稱所示,不管系統狀態如何,都會強制生成執行緒的Dispatch Queue。

這8種Global Dispatch Queue各使用1個pthread_workqueue。GCD初始化時,使用pthread_workqueue_create_np函式生成pthread_workqueue。 

pthread_workqueue包含在Libc提供的pthreads API中。其使用bsdthread_register和workq_open系統呼叫,在初始化XNU核心的workqueue之後獲取workqueue資訊。

XNU核心持有4中workqueue。

  • WORKQUEUE_HIGH_PRIOQUEUE
  • WORKQUEUE_DEFAULT_PRIOQUEUE
  • WORKQUEUE_LOW_PRIOQUEUE
  • WORKQUEUE_BG_PRIOQUEUE

以上為4種執行優先順序的workqueue。該執行優先順序與Global Dispatch Queue的4種執行優先順序相同。

 

Dispatch Queue中執行Block的過程。

當在Global Dispatch Queue中執行Block時,libdispatch從Global Dispatch Queue自身的FIFO佇列中取出Dispatch Continuation,呼叫pthread_workqueue_additem_np函式。將該Global Dispatch Queue自身、符合其優先順序的workqueue資訊以及為執行Dispatch Continuation的回撥函式等傳遞給引數。

  

pthread_workqueue_additem_np函式使用workq_kernreturn系統呼叫,通知workqueue增加應當執行的專案。根據該通知,XNU核心基於系統狀態判斷是否需要生成執行緒。如果是Overcommit優先順序的Global Dispatch Queue,workqueue則始終生成執行緒。

workqueue的執行緒執行pthread_workqueue函式,該函式呼叫libdispatch的回撥函式。在該回撥函式中執行加入到Dispatch Continuation的Block。

Block執行結束後,進行通知Dispatch Group結束、釋放Dispatch Continuation等處理,開始準備執行加入到Global Dispatch Queue中的下一個Block。

以上就是Dispatch Queue執行的大概過程。

 

三、執行緒和佇列

執行緒是程式碼執行的路徑,佇列則是用於儲存以及管理任務的,執行緒負責去佇列中取任務進行執行。

1、佇列

是管理執行緒的,相當於執行緒池,能管理執行緒什麼時候執行。

佇列分為序列佇列和並行佇列等

序列佇列:佇列中的任務按順序執行

並行佇列:佇列中的任務會併發執行。任務執行完畢了,不一定出佇列。只有前面的任務執行完了,才會出佇列。

 

序列佇列:佇列中的任務只會順序執行,多個序列佇列可並行執行

dispatch_queue_t q = dispatch_queue_create(“xxx”,DISPATCH_QUEUE_SERIAL);

 

並行佇列:佇列中的任務會併發執行

dispatch_queue_t q = dispatch_queue_create(“xxx”, DISPATCH_QUEUE_CONCURRENT);

 

全域性佇列:與並行佇列類似,但除錯時,無法確認操作所在佇列

dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0);

 

主佇列:每一個程式對應唯一一個主佇列;在多執行緒開發中,使用主佇列更新UI

dispatch_queue_t q = dispatch_get_main_queue();

 

2、同步和非同步

dispatch_async (非同步操作函式),就是將指定的Block“非同步”地追加到指定的佇列(queue)中。dispatch_async函式不做任何等待。會新開執行緒

 

 

 

 

dispatch_sync( 同步操作函式),就是將指定的Block“同步”地追加到指定的佇列(queue)中。在追加Block結束之前,dispatch_sync函式會一直等待;不會新開執行緒

 

 

   

3、佇列和操作的組合

序列佇列同步操作:同步操作不會新開執行緒、操作順序執行

序列佇列非同步操作:非同步操作新開一個子執行緒、操作順序執行,“最安全的選擇”

並行佇列同步操作:同步操作不會新開執行緒、操作順序執行

並行佇列非同步操作:非同步操作會新開多個執行緒(有多少任務,就開n個執行緒執行)、操作無序執行;佇列前如果有其他任務,會等待前面的任務完成之後再執行;場景:既不影響主執行緒,又不需要順序執行的操作! 

全域性佇列非同步操作:非同步操作會新建多個執行緒、操作無序執行,佇列前如果有其他任務,會等待前面的任務完成之後再執行

全域性佇列同步操作:同步操作不會新建執行緒、操作順序執行

主佇列非同步操作:非同步操作都在主執行緒上順序執行的,不存在非同步的概念

主佇列同步操作:會死鎖

 

4、會引起死鎖的2種情況

1、在主執行緒中運用主佇列同步。

- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_sync(dispatch_get_main_queue(), ^{      

        NSLog(@"hello");

    });

}

同步對於任務是立刻執行的,那麼當把任務放進主佇列時,它就會立馬執行,只有執行完這個任務,viewDidLoad才會繼續向下執行。

而viewDidLoad和任務都是在主佇列上的,由於佇列的先進先出原則,任務又需等待viewDidLoad執行完畢後才能繼續執行,viewDidLoad和這個任務就形成了相互迴圈等待,就造成了死鎖。

想避免這種死鎖,可以將同步改成非同步dispatch_async,或者將dispatch_get_main_queue換成其他序列或並行佇列,都可以解決。

 

 2、在序列佇列中同步的向這個序列佇列追加Block

 dispatch_queue_t serialQueue = dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);

 dispatch_async(serialQueue, ^{

        dispatch_sync(serialQueue, ^{

            NSLog(@"hello");

        });

});

想避免這種死鎖,可以將同步改成非同步dispatch_async,或者將序列佇列換為並行佇列,都可以解決。

 

以上部分內容參考自《Objective-C高階程式設計》一書

 

相關文章