GCD

概念

多線程編程 先來看一下下面的代碼。

int main() {
  id o = [[MyObject alloc] init];
  [o execBlock];
  return 0;
}

上面的代碼最終會轉化為機器能夠識別的指令(二進制代碼)。然后這些命令在 CPU 中一句一句的執行。由于一個 CPU 一次只能執行一個命令,不能執行某處分開的并列的兩個命令,因此通過 CPU 執行的 CPU 命令就好像一條無分叉的大道,其執行不會出現分歧。這里所說的“一個 CPU 執行的 CPU 命令為一條無分叉路徑”即為“線程”。

在單個 CPU的情況下, 使用多線程的程序可以在某個線程和其他線程之間反復的進行上下文切換,因此看上去好像一個 CPU 可以并列執行多個線程。在具有多個 CPU 的情況下,就是真的提供了多個 CPU 并行執行多個線程的任務了。

當然多線程編程也會帶來一定的問題:

  • 多個線程更新相同的資源導致數據的不一致(數據競爭)
  • 多個線程相互等待導致的死鎖問題
  • 開啟多個線程會消耗大量內存

在 iOS 程序啟動時,最先執行的線程,就是主線程,主線程用來描繪用戶界面、處理觸摸屏幕的事件等。 如果在這個線程中進行長時間的操作,就會妨礙主線程的執行,妨礙主線程中的 RunLoop 更新,從而導致不能更新用戶界面、應用畫面長時間停滯等問題。

GCD (Grand Central Dispatch) 通過 GCD 開發者只需要定義想要執行的任務并追加到適當的 Dispatch Queue 中, GCD 就能生成必要的線程并計劃執行任務。它將線程管理作為系統的一部分來實現的,因此可統一管理,這樣就相對來說更加有效率。

GCD 簡單使用

重要知識點

  • async/sync
    • sync 同步任務,會堵塞當前線程等待任務執行完
    • async 異步任務,不會堵塞當前線程,只要有任務就會繼續執行
  • Dispatch Queue(列隊)
    • Serial Dispatch Queue (等待前一個任務完成后才能繼續執行下一個任務)
      • Global Dispatch Queue(系統系統的全局的同步列隊)
    • Concurrent Dispatch Queue(不必等待前一個任務執行完成也可以繼續執行下一個任務)
      • Main Dispatch Queue(系統主線程,主要繪制用戶界面、處理屏幕事件)

使用方法: 將指定的任務追加到適當的 Dispatch Queue 中

//Swift
DispatchQueue.global().async {
    /**
     *想要執行的任務
     */
}
//Objective-C   
dispatch_async(queue, ^{
  /*
   *想要執行的任務
   */
})

創建列隊

  • Objective-C
//Serial Dispatch Queue
dispatch_queue_t serailDispatchQueue = dispatch_queue_create("com.cbreno.gcd.objective.serial, NULL);

dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.cbreno.gcd.objectivec.serial", DISPATCH_QUEUE_SERIAL);                                                            
//Concurrent Dispatch Queue
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.cbreno.gcd.objectivec.concurrent", DISPATCH_QUEUE_CONCURRENT);
  • Swift
//Serial Dispatch Queue 
let serialQueue = DispatchQueue(lable: "com.cbreno.gcd.swift.serial")
//Concurrent Dispatch Queue
let concurrentQueue = DispatchQueue(lable: "com.cbreno.gcd.swift.concurrent", attributes: .concurrent)
  • Serial Dispatch Queue 一旦生成并且追加處理,系統對于一個 Serial Dispatch Queue 就只生成并使用一個線程。如果過多使用多線程,會消耗大量內存,引起大量的上下文切換,大幅度江都一同的響應性能。
  • 為了避免多線程更新相同資源導致數據競爭問題,使用 Serial Dispatch Queue。
  • Dispatch Queue 的名稱推薦使用應用程序ID的逆序全程域名,這么做的好處是在Xcode 和 Instruments 調試的時候很方便觀察。另外應用奔潰時產生的 CrashLog 中很容易找到對應線程。

Main Dispatch Queue/Global Dispatch Queue

  • Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//Swift中這是一個 DispatchQueue 類型的變量
let mainQueue = DispatchQueue.main

Main Dispatch Queue 是主線程中執行的 Dispatch Queue ,是一個 Serial Dispatch Queue。追加到 Main Dispatch Queue 的處理在主線程的 RunLoop 中執行。主線程主要處理用戶界面的渲染、觸摸事件等, 所以需要長時間的操作,最好在別的線程中執行,執行完后再在主線程中刷新UI等操作。

  • Global Dispatch Queue
//Objective-C
//DISPATCH_QUEUE_PRIORITY_HIGH
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

//DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//DISPATCH_QUEUE_PRIORITY_LOW
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

//DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//Swift
//userInitiated
let globalQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated)

//default
let globalQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)

//utility
let globalQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.utility)

//background
let globalQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.background)
 //在Swift中優先級是一個 enum 類型
   public enum QoSClass {

        case background

        case utility

        case `default`

        case userInitiated

        case userInteractive

        case unspecified

        public init?(rawValue: qos_class_t)

        public var rawValue: qos_class_t { get }
    }
  • 我們可以直接使用系統提供的這兩個Dispatch Queue 幫助我們完成一般操作,像下面一樣:
//Objective-C
//Global Dispatch Queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) ^{
  //處理長時間的操作
      .....
  //使用Main Dispatch Queue 在主線程中操作
  dispatch_async(dispatch_get_main_queue(), ^{
    //只能在主線程中執行的操作
  });
});
//Swift
//Global Dispatch Queue
DispatchQueue.global().async {
//處理長時間的操作
    .....
  //使用Main Dispatch Queue 在主線程中操作
    DispatchQueue.main.async {
    //只能在主線程中執行的操作
    }
}
  • Dispatch After
    當需要延遲執行的情況就要用到 Dispatch After,在指定的時間后將指定的 Block 追加到指定的Dispatch Queue 中。
//Objective-C
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
  ..........
});
  • 第一個參數是指定需要延遲的時間,是dispatch_time_t 類型的值。

    dispatch_time 方法的第一個參數是 dispatch_time 類型的有兩種值:

 //A somewhat abstract representation of time; where zero means "now" and
  #define DISPATCH_TIME_NOW (0ull) 
  //DISPATCH_TIME_FOREVER means "infinity" and every value in between is an
  #define DISPATCH_TIME_FOREVER (~0ull)

dispatch_time 方法的第二個參數有以下幾種類型:

#define NSEC_PER_SEC 1000000000ull
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull  

ull 表示 unsigned long long

  • 第二個參數是需要追加處理的 Dispatch Queue。

  • 第三個參數是需要追加的 Block。

//Swift
let delay = DispatchTime.now() + DispatchTimeInterval.seconds(60)
//let delay = DispatchTime.now() + 3.0
DispatchQueue.main.asyncAfter(deadline: delay) { 
    //需要延遲執行的任務
}
  • 在定義延遲時間的時候可以直接在 DispatchTime.now() 方法后面加上需要的延遲的時間,是因為蘋果定義了 + 方法,使得它可以直接操作。
//Swift
public func +(time: DispatchTime, seconds: Double) -> DispatchTime

當然還定義了一些其他的方法:

//Swift
public func +(time: DispatchWallTime, seconds: Double) -> DispatchWallTime

public func +(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime

public func +(time: DispatchWallTime, interval: DispatchTimeInterval) -> DispatchWallTime

public func -(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime

public func -(time: DispatchTime, seconds: Double) -> DispatchTime

public func -(time: DispatchWallTime, interval: DispatchTimeInterval) -> DispatchWallTime

public func -(time: DispatchWallTime, seconds: Double) -> DispatchWallTime

public func <(a: DispatchTime, b: DispatchTime) -> Bool

public func <(a: DispatchWallTime, b: DispatchWallTime) -> Bool

public func ==(a: DispatchTime, b: DispatchTime) -> Bool

public func ==(a: DispatchQoS, b: DispatchQoS) -> Bool

public func ==(a: DispatchWallTime, b: DispatchWallTime) -> Bool

Dispatch Group

如果有這樣的需求,需要等待所有的線程中的任務都執行完成后,做統一的處理。通常為多個網絡請求操作,需要等待所有的網絡請求完成后,解析數據,統一刷新UI。這時候我們就可以通過 Dispatch queue 來完成我們的操作。當然在使用一個 Serial Dispatch Queue 的時候只要將所有的任務追加到其中并且在最后處理結果即可實現。但是在使用 Concurrent Dispatch Queue 的時候就使用 Dispatch Group 比較方便。

//Objective-C
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(@"bloack0");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"bloack1");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"bloack2");
});
    
    //在所有任務執行完成后,通知。。。
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"done");
});
//Swift
 let group = DispatchGroup()
        
let queue = DispatchQueue.global()
        
    queue.async(group: group) { 
        print("TEST0")
    }
    queue.async(group: group) {
        print("TEST1")
    }
    queue.async(group: group) {
        print("TEST2")
    }
        
    group.notify(queue: DispatchQueue.main) { 
        print("done")
    }

Dispatch barrier

在一些情況下,當我們在處理一些事情的時候,要處理另外一些事情的時候,需要停止當前做的事情的時候,我們可能需要 Dispatch barrier 來幫助我們完成任務。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"read0");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"read1");
    });
    
    //在讀取數據的過程中,執行寫操作,就要停止讀操作,只執行寫操作,這樣不會造成數據沖突的問題。
    dispatch_barrier_async(queue, ^{
        NSLog(@"write");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"read2");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"read3");
    });

相關資料

Swift 3必看:從使用場景了解GCD新API(卓同學)
Concurrent Programming With GCD in Swift 3
關于iOS多線程,你看我就夠了

這篇大部分是對《Objective-C 高級編程 iOS 與 OS X 多線程和內存管理》這本書的總結。 暫時寫這么多了。。之后再補充吧。。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

  • 我們知道在iOS開發中,一共有四種多線程技術:pthread,NSThread,GCD,NSOperation: ...
    請叫我周小帥閱讀 1,500評論 0 1
  • Grand Central Dispatch(GCD)概要 我的博客鏈接 什么是GCD? 蘋果官方這么描述的:Gr...
    換個名字再說閱讀 1,294評論 4 7
  • 簡介 GCD(Grand Central Dispatch)是在macOS10.6提出來的,后來在iOS4.0被引...
    sunmumu1222閱讀 880評論 0 2
  • 章節目錄 什么是GCD? 如何在多條路徑中執行CPU命令列? 即使多線程存在很多問題(如數據競爭、死鎖、線程過多消...
    DrunkenMouse閱讀 875評論 1 13
  • 多線程概念 線程線程指的是:1個CPU執行的CPU命令列為一條無分叉路徑 多線程這種無分叉路徑不止一條,存在多條即...
    我系哆啦閱讀 601評論 0 5