多線程-GCD

GCD(grand central dispatch)概要

什麼是GCD

是異步執行任務的技術之一。它將應用程序中線程管理的代碼放在系統級中實現。開發者只須要定義想執行的任務並追加到適當的Dispatch Queue中,至於具體是哪一個線程執行、如何執行該任務,開發者不需管,交由GCD來管理。編程

所以GCD優於其餘異步技術的點:線程管理交由系統管理,效率更高。bash

線程

源碼通過編譯連接後生成二進制目標代碼(CPU命令列),CPU順序執行這些命令列。 一個CPU核執行CPU命令列是一條無分叉的路徑,即爲線程。當一個線程被掛起時,當前CPU的寄存器等信息會保存到各自路徑專用的內存塊中(保存上下文),線程恢復繼續執行,則從內存塊中取出數據,復原CPU寄存器等信息,繼續執行切換路徑的CPU命令列(上下文切換)。多線程

多線程編程容易發生的問題:併發

多線程的優勢:app

GCD的API

總覽圖: 異步

Dispatch Queue

Dispatch Queue:任務隊列,存儲等待執行的任務。它會按照追加順序(FIFO)執行任務async

隊列種類

  1. 串行隊列(Serial Dispatch Queue):其餘任務要等待如今執行中的任務處理結束(一個隊列對應1個線程(線程可變),即同時只能執行一個任務),有序。
  2. 並行隊列(Concurrent Dispatch Queue):其餘任務不需等待如今執行中的任務處理結束(一個隊列對應>=1個線程,具體多少要看當前的系統資源,即同時可執行多個任務),無序。

串行隊列缺點:每一個串行隊列對應一個線程,當同時建立多個串行隊列時,對應多個線程。此時可能會發生數據競爭。 而一個並行隊列雖然同時會有多個線程,可是無論生成多少線程,都會有XNU內核來管理,因此不需擔憂串行隊列的問題。函數

獲取隊列的方式

  1. 本身建立:dispatch_queue_create
// DISPATCH_QUEUE_SERIAL/NULL/0 串行隊列
    // DISPATCH_QUEUE_CONCURRENT    並行隊列
    // ARC狀況不須要手動釋放
    dispatch_queue_t queue = dispatch_queue_create("queueIdentifier", 0);
    dispatch_async(queue, ^{
        NSLog(@"執行任務");
    });
複製代碼

  1. 獲取系統提供的隊列

主隊列:oop

dispatch_queue_t mainQueue = dispatch_get_main_queue();
複製代碼

主隊列是在main()被調用前,自動被系統建立的串行隊列,和主線程相關聯。(但這並不表明主隊列中的任務只能被主線程執行)。ui

若是想要(其餘線程)執行主隊列中的任務,只能用如下三種方式中的一種:

  • 調用dispatch_main:叫停主線程,等待主隊列中的任務執行(其餘線程),永遠不會返回
  • 調用UIApplicationMain (iOS) or NSApplicationMain (macOS):建立應用、應用代理、和事件循環,永遠不會返回
  • 在主線程上使用一個CFRunLoopRef

全局並行隊列:Global Dispatch Queue

/*
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
     */
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
複製代碼

注意:使用函數本身建立的隊列的優先級和globalQueue中默認優先級是同等水平的。若是想要改變隊列的優先級須要使用函數dispatch_set_target_queue。

變動手動建立的隊列的優先級

dispatch_set_target_queue(要改變的對象obj, 目標隊列tq)
複製代碼

給一個隊列設置目標隊列會改變這個隊列的某些行爲:

  • 分發隊列
    • 隊列自己未設置優先級,則它的優先級會繼承目標隊列的優先級;
    • 目標隊列決定了obj的銷燬函數在哪一個隊列上被執行;
    • 一般不會對已經加入的任務形成影響;
    • obj會持有tq;
    • obj若是是globalQ、mainQ,無效;
  • 分發資源
  • 分發I/O通道

注意:設置目標隊列是,不要造成繼承循環。

  1. 目標隊列爲串行隊列:
dispatch_queue_t serialQ = dispatch_queue_create("serialQ", NULL);
    dispatch_queue_t sQ2 = dispatch_queue_create("serialQ2", NULL);
    dispatch_queue_t sQ3 = dispatch_queue_create("serialQ3", NULL);
    
    dispatch_async(serialQ, ^{
        NSLog(@"serialQ_1---------->");
    });
    dispatch_async(sQ3, ^{
        NSLog(@"serialQ_3----------->");
    });
    dispatch_async(sQ2, ^{
        NSLog(@"serialQ_2----------->");
    });
    此時結果是不定的,由於三個serialQueue是並行的。
複製代碼
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t serialQ = dispatch_queue_create("serialQ", NULL);
    dispatch_queue_t sQ2 = dispatch_queue_create("serialQ2", NULL);
    dispatch_queue_t sQ3 = dispatch_queue_create("serialQ3", NULL);
    
    dispatch_set_target_queue(serialQ, globalQ);
    dispatch_set_target_queue(sQ2, serialQ);
    dispatch_set_target_queue(sQ3, serialQ);
    
    dispatch_async(serialQ, ^{
        NSLog(@"serialQ_1---------->");
    });
    dispatch_async(sQ3, ^{
        NSLog(@"serialQ_3----------->");
    });
    dispatch_async(sQ2, ^{
        NSLog(@"serialQ_2----------->");
    });
    此時結果是肯定的 一、三、2。而且使用的是同一個線程
複製代碼

正常狀況下:不管是串行仍是併發隊列,只要設置了目標隊列,以後任務都會向上傳到它的上一級目標隊列中。所以任務其實最後都傳到根上去了。因此如今這條鏈上的隊列應該都是公用一個線程池的。所以若是要獲取當前隊列,即獲取執行當前代碼所關聯的隊列,結果是不定的。

GCD線程池中,系統最多會建立64個線程。手動建立的線程系統會默認設置目標隊列:全局默認優先級隊列。
即便是加入到併發隊列中的任務:

  1. 目標隊列是並行隊列:

定時追加任務--dispatch_after()

設置必定時間後,將任務異步添加到隊列中(而不是將任務加到隊列中,必定時間後執行),所以時間可能不夠準確。

函數直接返回。

  1. 相對時間
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"執行任務");
    });
複製代碼
  1. 絕對時間 設置在某一具體時間(2011/3/25-11:00:03)時,將任務異步添加到隊列中。
- (dispatch_time_t)getDispatchTimeByDate:(NSDate *)date {
    NSTimeInterval interval;
    double second, subsecond;
    struct timespec time;
    dispatch_time_t milestone;
    
    interval = [date timeIntervalSince1970];
    subsecond = modf(interval, &second); // 取浮點數的小數部分和整數部分
    time.tv_sec = second;
    time.tv_nsec = subsecond * NSEC_PER_SEC; // 小數部分轉納秒
    milestone = dispatch_walltime(&time, 0);
    
    return milestone;
}
複製代碼

Dispatch Group

當但願知道加入到隊列中的任務什麼時候所有執行完成時,有兩種方式:1. 將任務加入到串行隊列,結束的任務加到最後便可。2. 任務加到多個隊列中或者加到並行隊列中,須要用到group。

  1. notify

代碼解析:

// 將任務block異步加到隊列中,而且將block關聯到group羣組中,即block持有group
    dispatch_group_async(group, globalQ, ^{
        NSLog(@"global_blk0---->%@",[NSThread currentThread]);
    });
    // 當持有group的全部任務都執行完成後,會將任務加入到隊列中去執行。若是group中無任務,則當即將任務加到指定隊列中
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"done---->%@",[NSThread currentThread]);
    });
複製代碼
  1. wait

dispatch_barrier_async

訪問數據時,容易出現數據競爭的問題。 寫操做不容許其餘讀寫操做並行執行,讀操做可與其餘讀操做並行執行。

須要dispatch_barrier_async爲須要單獨執行的操做加入柵欄。

代碼:

同步執行--dispatch_sync()

相似於前面的dispatch_wait,線程執行到該函數,會進入等待狀態,直到同步的任務結束,纔會返回,繼續執行。

容易形成的問題:死鎖

// 線程進入等待狀態,直到barrierBlock執行完成纔會返回
dispatch_barrier_sync(serialQ, ^{
});
複製代碼

dispatch_apply

dispatch_apply函數結合了dispatch_sync和dispatch group的功能。該函數按照指定次數將任務block添加到隊列中,並等待所有任務執行完成,再返回。

併發隊列:

串行隊列:

應用:(不關心數據順序時)

dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(global, ^{
        dispatch_apply([array count], global, ^(size_t i) {
            NSLog(@"%@", array[i]);
        });
    });
複製代碼

暫停和恢復隊列中的任務

  1. dispatch_suspend(dispatch_object_t): 掛起指定隊列,已經執行的任務不受影響,還沒有執行的中止執行;
  2. dispatch_resume(dispatch_object_t): 恢復指定隊列,繼續執行隊列中的任務。

注意:

① 1,2必須是一對一的,由於執行1,dispatch_object_t的suspension count會+1;執行2會減一。當count>0時,會一直處於掛起狀態。

② 當要暫停一個隊列時,上述函數僅對本身手動建立的隊列有效,對於系統隊列(global、main)無效的。

  1. iOS 8以後,dispatch_block_cancel(dispatch_block_t)能夠取消任務,可是僅能取消等待執行的任務,不能取消正在執行的任務。

信號量-- dispatch semaphore

思想相似停車場:初始有n個車位,每來一輛車,先查看n是否大於0,若是n>0,車輛進入使用車位,n--。每輛車離開停車場n++。 當初始值設置爲1時,能夠做爲鎖來使用。

保證任務只執行一次--dispatch_once

dispatch_once函數保證在整個應用程序執行中,只執行一次指定任務,即便在多線程環境下。

應用:單例模式

static NSObject *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[NSObject alloc] init];
    });
    return instance;
複製代碼

Dispatch I/O

讀取較大文件時,可將其分割成合適大小併發讀取,提升讀取速度。

相關文章
相關標籤/搜索