GCD(Grand_Central_Dispatch) 詳解-

什么是線程?####

當一個應用程序安裝到 Mac 或 iPhone上的時候, Mac、iPhone 的操作系統 OSX, iOS 會根據用戶的姿勢啟動該應用程序后,一個一個地執行CPU命令行,先執行一個命令,再接著執行另一個命令,這樣不斷的循環下去。

CPU 一次只能執行一個命令, 不能執行某處分開的并行的兩個命令,因此通過 CPU 執行的 CPU 命令就好比一份無分叉的大道,其執行不會出現分歧。所以,1個CPU 執行的CPU命令列為一條無分叉路經即為線程;

但,這種無分叉路經不只一條,存在多條時候,即為多線程,1 個 CPU 核執行多條不同路徑上的不同命令。 雖然CPU相關技術很多, 但基本上1個CPU核一次能夠執行的CPU命令始終為1。 OS X和 iOS 的內核在發生操作系統事件的時候,會切換執行路經,執行中路經的狀態,例如CPU的寄存器等信息保存到各自路經專用的內存中,從切換目標路經專用的內存中復原CPU寄存器等信息,繼續執行切換路經的 CPU 命令行,這個被稱為`上下文切換`;因而其實線程之間一直進行著上下文切換。

多線程容易出現問題,如 多個線程更新相同的資源會導致數據的不一致(數據競爭)、停止等待時間的線程會導致多個線程相互持續等待(死鎖)、使用太多線程會消耗大量內存等。

死鎖
數據競爭.png
大量線程.png

即使有問題,也要采用多線程,這樣才能保證應用程序的響應性能,啟動后, 最先執行的線程(主線程)描繪界面、處理觸摸屏幕的時間等等,若是在主線程中進行大量的數據操作便會阻塞主線程,從而導致沒法再更新用戶界面,出現長時間卡頓。

主線程卡頓.png

GCD大大簡化了偏于復雜的多線程的源代碼。

什么是GCD?####

GCD是異步執行任務的技術之一, 一般將應用程序中記述的線程管理用的代碼在系統級中的實現,開發者只需要定義想執行的任務并追加到適當的 Dispatch Queue 中,GCD 就能生成必要的線程并計劃執行任務,由于線程管理是作為系統的一部分來實現的,因此可以統一管理,也可執行任務,這樣就比以前的線程更有效率。

也就是說,GCD 用我們難以置信的非常簡潔的記述方法,實現了極為負責繁瑣的多線程編程,可以說這是一項劃時代的技術。

dispatch_async(queue, ^{
// 這一行的代碼表示處理在后臺線程執行
/*
 * 長時間處理的工作
 * 如一些復雜的數據處理
 */
       dispatch_async(dispatch_get_main_queue(),^{
 // 這代碼表示在主線程中執行
 /*
  * 只在主線程可以執行的處理
  * 例如用戶界面更新
  */    
  });     
 });
Disaptch Queue#####

開發者要做的只是定義想執行的任務并追加到適當的Dispatch Queue中。

就如同:
 
dispatch_async(queue, ^{
 /*
  * 想執行的任務
  */
 })

使用了 Block 語法“定義想執行的任務”,通過 dispatch_async 函數“追加”賦值在變量 queue 的 “Dispatch Queue 中”,這樣就可以指定 Block 在另一個線程中執行?!啊盌ispatch Queue 按照追加的順序(FIFO)執行處理。

在執行處理時候,存在兩種 Disparch Queue, 一種是等待執行中處理的 Serial Dispatch Queue, 另一種是不等待現在執行中處理的 Concurrent Dispatch Queue。

dispatch_async(queue, block0);
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_async(queue, block3);           
dispatch_async(queue, block4);
dispatch_async(queue, block5);

如以上代碼,當 queue 為 Serial Dispatch Queue 時候,因為要等待現在執行中的處理結果,所以順序為

block0
block1
block2
block3
block4
block5

當變量 queue 為 Concurrent Dispatch Queue 時,因為不用等待現在執行中的處理結束,所以執行了 block0 不不管其是否執行結束都執行 block1, 如此重復。

iOS 和 OS X 的核心 --XNU 內核決定應當使用的線程數,并只生成所需要的線程執行處理,當處理結束,應當執行的處理數減少時,XNU 內核會結束不再需要的線程。
dispatch_queue_create#####

通過 Dispatch_queue_create 函數生成 Dispatch Queue。

      dispatch_queue_t mySyrialQueue = dispatch_queue_create("com.queue.serialQueue", DISPATCH_QUEUE_SERIAL);
      dispatch_async(mySyrialQueue, ^{
    NSLog(@"mySyrialQueue block");
      });
      
      // 第一個參數指定 Queue 的名稱,建議使用應用程序 ID 這種命名方式, 當然也可以設置為 NULL;
      // 第二個參數指定  DISPATCH_QUEUE_SERIAL 或 NULL 表示 生成 Serial Dispatch Queue 

雖然 Serial Dispatch Queue 同時只能追加一個處理,但當生成多個 Serial Dispatch Queue 時,各個 Serial Dispatch Queue 將并行執行,即同時執行多個。系統對于一個 Serial Dispatch Queue 就只能生成并使用一個線程, 如果生成2000個 Serial Dispatch Queue, 那么就可以生成2000多個線程。如果過多的使用線程,就會消耗大量的內存,引起大量的上下文切換,大幅度降低系統的響應性能。

所以, 盡量在“避免多個線程更新相同資源導致數據競爭時”使用 Serial Dispatch Queue

使用 Serial Dispatch Queue
    dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.queue.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(myConcurrentQueue, ^{
    NSLog(@"myConcurrentQueue block");
    });
    // 第一個參數指定 Queue 的名稱,建議使用應用程序 ID 這種命名方式, 當然也可以設置為 NULL;
    // 第二個參數指定  DISPATCH_QUEUE_CONCURRENT 生成 Concurrent Dispatch Queue 
Main Dispatch Queue/Global Dispatch Queue

除了創建線程之外, 系統還提供了標準的 Dispatch Queue, 就是 Main Dispatch Queue 和 Global Dispatch Queue。

  • Main Dispatch Queue
    主線程, 是一個 Serial Dispatch Queue,追加到主線程的處理在主線程的 RunLoop 中執行, 由于在主線程中執行,因此要將用戶界面的界面更新等一些必須在主線程中執行的處理追加到 Main Dispatch Queue 使用。

  • Global Dispatch Queue
    Global Dispatch Queue 是所有的應用程序都能使用的 Concurrent Dispatch Queue,沒有必要通過 dispatch_queue_create 函數來逐個生成 Concurrent Dispatch Queue。只要獲取即可。它包含四個執行優先級。

    • 高優先級(High Priority)
    • 默認優先級(Default Priority)
    • 低優先級(Low priority)
    • 后臺優先級(Background Poriority)

    在向 Global Dispatch Queue 追加處理的時候,應選擇與處理內容對應的執行優先級的 Global Dispatch Queue。但是并不能保證實時性,因此優先級只是大致的判斷。

     // Main Dispatch Queue 的獲取方法
     dispatch_queue_t mainDisatchQueue = dispatch_get_main_queue();

     // Global Dispatch Queue(高優先級)的獲取方法
 
     dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
     DISPATCH_QUEUE_PRIORITY_HIGH        // 高優先級
     DISPATCH_QUEUE_PRIORITY_LOW         // 低優先級
     DISPATCH_QUEUE_PRIORITY_DEFAULT     // 默認優先級
     DISPATCH_QUEUE_PRIORITY_BACKGROUND  // 后臺優先級
      
     // 使用 Main Dispatch Queue 和 Global Dispatch Queue 源碼
     /*
      * 在默認的優先級的 Global Dispatch Queue 中執行 block
      */
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
      //  可并行執行的處理
      //  在 main 中block
      dispatch_async(dispatch_get_main_queue(), ^{
        
        // 在 主線程中 執行 Block
        });
       
     });
dispatch_after#####
  • 在指定時間后執行處理的情況,可以使用 dispatch_after, 需要注意的是 dispatch_after 并不是在指定時間后執行處理,而是在指定時間追加處理到 Dispatch Queue。
  dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_MSEC);
  
    // ull為C語言數值字面量。
    
  dispatch_after(time, dispatch_get_main_queue(), ^{
 
   NSLog(@"waited at least three secons");
  });
   // 第一個參數 指定時間用的 dispatch_time_t 類型的值 使用 dispatch_time 函數或者 dispatch_walltime 函數構成
   // dispatch_time_t 表示相對時間, dispatch_walltime 表示絕對時間
  
 以上代碼,因為 Main Dispatch Queue 在主線程的 RunLoop 中執行,所以必入每隔 1/60 秒執行的 RunLoop 中, Block 最快在3秒后執行,最慢也在3+1/60秒后執行,
  • dispatch_time_t 函數
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_MSEC);
    
 函數獲取從第一個參數 制定的時間開始到第二個參數指定的毫微秒單位時間后的時間。第一個參數經常使用的值是 `DISPATCH_TIME_NOW` 表示現在的時間。 "ull"表示 C 語言的數字面量(表示“unsigned long long”)
      
    NSEC_PER_MSEC 表示以毫秒為單位
    NSEC_PER_SEC  表示為秒
Dispatch Group#####

在追加到 Dispatch Queue 中的多個處理全部結束后限制性結束處理, 這種情況經常出現, 當為 Serial Dispatch Queue 時候, 只要將想要執行的處理全部追加到 Serial Dispatch Queue 中并在最后追加結束處理即可實現,但是要使用 Concurrent Dispatch Queue 時或同事使用多個 Dispatch Queue 時候,源碼就會變得頗為復雜。
此種情況適合使用 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(@"block1");});
dispatch_group_async(group, queue, ^{NSLog(@"block2");});
dispatch_group_async(group, queue, ^{NSLog(@"block3");});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"done");
});

// 執行結果
block1
block2
block3
done

因為向 Global Dispatch Queue 即 Concurrent Dispatch Queue 追加處理,多個線程并行處理執行,所以追加處理的執行順序不定,執行時候會發生變化,但是此執行結果的 done 一定是最后輸出的。

無論向什么樣的 Dispatch Queue 中追加處理, 使用 Dispatch Group 都可監視這些處理執行的結束,一旦監測到所有的處理執行結束, 就可以結束的處理追加到 Dispatch Queue 中, 這就是使用 Dispatch Group 的原因。

dispatch_group_notify 函數第一個參數為指定的要監視的 Dispatch Group. 在追加到該 Dispatch Group 的全部處理執行結束后,將第三個參數的 block 追加第二個參數的 Dispatch Queue 中, 在 dispatch_group_notify 函數中不管指定什么樣的 Dispatch Queue。屬于 Dispatch Group 的全部處理在追加到指定的 Block 時都已執行結束。

除了 dispatch_group_async 之外,還可以使用dispatch_group_wait 僅等待全部處理執行結束。

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(@"block1");});
dispatch_group_async(group, queue, ^{NSLog(@"block2");});
dispatch_group_async(group, queue, ^{NSLog(@"block3");});

NSInteger i = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 如果 i = 0 表示處理結束, 反之 Group 并沒執行完。

 第二個參數,為 dispatch_time_t 類型,為等待的時間, 假設block1里執行了復雜的耗時操作超過一秒, 當第二個參數為小于1 秒時, 這 `dispatch_group_wait` 返回的 肯定不為0 則表示為 Group 并沒執行完, 假設第二個參數為 `DISPATCH_TIME_FOREVER` ,意味永久等待,所以只要 Group 的處理尚未執行結束, 就會一直等待, 中途不能取消。 也就 `dispatch_group_wait` 返回的值也一定為0;
dispatch_barrier_async#####

訪問數據庫或者文件的時候,如前所述,使用 Serial Dispatch Queue 可避免數據競爭的問題。 寫入數據處理不可與其他寫入數據處理以及包涵讀取處理的其它某些處理并行執行,但是如果讀取處理只是與讀取處理并行執行, 那么多個并行執行就不會發生問題。
首先,用 dispatch_queue_create 函數生成 Concurrent Dispatch Queue, 在 dispatch_async 中追加讀取處理。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.forBarrier", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, block0_for_reading);
    dispatch_async(queue, block1_for_reading);
    // 進行寫入操作
    dispatch_async(queue, block0_for_writing);
    //
    dispatch_async(queue, block2_for_reading);
    dispatch_async(queue, block3_for_reading);

若按此代碼執行, 在 bock1 與 block2 之間執行寫入操作,就可能會在追加寫入之前的處理中讀取到與期待不符合的數據,還可能因為非法的訪問導致應用程序一場。若采用以下操作:

       dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.forBarrier", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, block0_for_reading);
    dispatch_async(queue, block1_for_reading);
    // 進行寫入操作
    dispatch_barrier_async(queue, block0_for_writing);
    //
    dispatch_async(queue, block2_for_reading);
    dispatch_async(queue, block3_for_reading);
dispatch_barrier_async函數.png

則會有效的防止出現的問題,因為 dispatch_barrier_async 函數會等待追加到 Concurrent Dispatch Queue 上的并執行的處理全部結束之后,再將指定的處理追加到該 Concurrent Dispatch Queue 中,然后由 dispatch_barrier_async 函數追加的處理執行完畢之后,Concurrent Dispatch Queue 的處理又開始執行。

以上內容來整理來自于 (Objective-C高級編程 iOS與OS X 多線程和內存管理)

gcd??戳這

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

推薦閱讀更多精彩內容