iOS多線程之GCD

iOS多線程之GCD

什么是GCD

GCD(grand central dispatch) 是 libdispatch 的市場名稱,而 libdispatch 作為 Apple 的一個庫,為并發代碼在多核硬件(跑 iOS 或 OS X )上執行提供有力支持。它具有以下優點:

  • GCD 能通過推遲昂貴計算任務并在后臺運行它們來改善應用的響應性能。
  • GCD 提供一個易于使用的并發模型而不僅僅只是鎖和線程,以幫助我們避開并發陷阱。
  • GCD 具有在常見模式(例如單例)上用更高性能的原語優化你的代碼的潛在能力。
相關概念
1. Serial vs. Concurrent 串行 vs. 并發

串行是指同一時間只有一個任務執行,并發是指同一時間可以執行多個任務。

2. Synchronous vs. Asynchronous 同步 vs. 異步

同步是指一個函數等函數體執行完了才返回,異步是指函數立即返回(此時函數體還沒有執行完成)。

3. Critical Section 臨界區

就是一段代碼不能被并發執行,也就是,兩個線程不能同時執行這段代碼。

4. Race Condition 競態條件

程序的輸出結果依賴于不同事件的執行順序。

5. Deadlock 死鎖

兩個(或多個)進程(或線程)都卡住了,等待對方完成或執行某些操作。第一個不能完成是因為它在等待第二個的完成。但第二個也不能完成,因為它在等待第一個的完成。

6. Thread Safe 線程安全

線程安全的代碼能在多線程或并發任務中被安全的調用,而不會導致任何問題(數據損壞,崩潰,等)。線程不安全的代碼在某個時刻只能在一個上下文中運行。一個線程安全代碼的例子是 NSDictionary 。你可以在同一時間在多個線程中使用它而不會有問題。另一方面,NSMutableDictionary 就不是線程安全的,應該保證一次只能有一個線程訪問它。

7. Context Switch 上下文切換

一個上下文切換指當你在單個進程里切換執行不同的線程時存儲與恢復執行狀態的過程。這個過程在編寫多任務應用時很普遍,但會帶來一些額外的開銷。

8. Concurrency vs Parallelism 并發與并行

并發是指在同一個時間段內可以有多個任務執行,但是同一時刻只能有一個任務執行。并行是指即使在同一時刻也有多個任務執行。

9. Queues 隊列

GCD 提供有 dispatch queues 來處理代碼塊,這些隊列管理你提供給 GCD 的任務并用 FIFO 順序執行這些任務。這就保證了第一個被添加到隊列里的任務會是隊列中第一個開始的任務,而第二個被添加的任務將第二個開始,如此直到隊列的終點。

10. Serial Queues 串行隊列

串行隊列中的任務一次執行一個,每個任務只在前一個任務完成時才開始。而且,你不知道在一個 Block 結束和下一個開始之間的時間長度,如圖



這些任務的執行時機受到 GCD 的控制;唯一能確保的事情是 GCD 一次只執行一個任務,并且按照我們添加到隊列的順序來執行。
由于在串行隊列中不會有兩個任務并發運行,因此不會出現同時訪問臨界區的風險。

11. Concurrent Queues 并發隊列

在并發隊列中的任務能得到的保證是它們會按照被添加的順序開始執行,但是無法保證任務的開始必須在上一個任務完成之后,也就是說上一個任務還沒完成下一個任務就開始了。任務可能以任意順序完成,你不會知道何時開始運行下一個任務,或者任意時刻有多少 Block 在運行。如圖



何時開始一個 Block 完全取決于 GCD 。如果一個 Block 的執行時間與另一個重疊,也是由 GCD 來決定是否將其運行在另一個不同的核心上,如果那個核心可用,否則就用上下文切換的方式來執行不同的 Block 。

12. Queue Types 隊列類型

首先,系統提供給你一個叫做 主隊列(main queue) 的特殊隊列。和其它串行隊列一樣,這個隊列中的任務一次只能執行一個。然而,它能保證所有的任務都在主線程執行,而主線程是唯一可用于更新 UI 的線程。這個隊列就是用于發生消息給 UIView 或發送通知的。
系統同時提供給你好幾個并發隊列。它們叫做 全局調度隊列(Global Dispatch Queues) 。
最后,你也可以創建自己的串行隊列或并發隊列。這就是說,至少有五個隊列任你處置:主隊列、四個全局調度隊列,再加上任何你自己創建的隊列。

使用
  1. dispatch_queue_create

         //創建一個串行隊列
         dispatch_queue_t serialQue = dispatch_queue_create"serialQueue", DISPATCH_QUEUE_SERIAL);
         
         //創建一個并發隊列
         dispatch_queue_t conQue = dispatch_queue_create("conCurrentQue", DISPATCH_QUEUE_CONCURRENT);
         
         //創建一個高優先級并發隊列
         dispatch_queue_attr_t queAttr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INTERACTIVE, -1);
         dispatch_queue_create(@"attrQue", queAttr);
    
  2. Main Dispatch Que Or Global Dispatch Que
    //獲取主隊列
    dispatch_queue_t mainQue = dispatch_get_main_queue();

         //獲取全局高優先級并發隊列
         //優先級
        //  QOS_CLASS_USER_INTERACTIVE > QOS_CLASS_USER_INITIATED > QOS_CLASS_UTILITY > QOS_CLASS_DEFAULT > QOS_CLASS_BACKGROUND
         dispatch_queue_t globalQue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    
  3. dispatch_set_target_queue
    使用dispatch_queue_create創建的queue不管是串行的還是并行的,都使用與默認優先級全局隊列相同的優先級。如果要變更生成隊列的優先級,就要使用此函數。生成一個高優先級的并發隊列的代碼入下:
    dispatch_queue_t highQue = dispatch_queue_create("highPri", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t gloablHighQue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    dispatch_queue_t gloablDefaultQue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);

     dispatch_set_target_queue(highQue, gloablHighQue);
             
     dispatch_async(gloablDefaultQue, ^{
             
     NSLog(@"this is defaulPri queue");
     });
     dispatch_async(highQue, ^{
             
     NSLog(@"this is highPri queue");
     });
    

    運行結果如下:

    2016-03-29 09:04:28.958 LZThreadPro04GCD48247:4511919 this is highPri queue
    2016-03-29 09:04:28.958 LZThreadPro04GCD48247:4511909 this is defaulPri queue

  4. dispatch_after
    可以讓指定任務延遲執行,如讓任務延遲3秒執行。
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

            NSLog(@"delayed action");
            
        });
    
  5. Dispatch Group
    在追加到隊列中的多個任務全部結束后想執行結束處理。下列代碼展示了當幾個任務全部結束后才做最后處理的情況。
    NSLog(@"1 + 2 + 3 + ... + 100 = ");
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    dispatch_queue_t mainQ = dispatch_get_main_queue();
    __block NSInteger sum1 = 0;
    __block NSInteger sum2 = 0;
    __block NSInteger sum3 = 0;
    __block NSInteger sum4 = 0;
    __block NSInteger sum5 = 0;
    __block NSInteger sum6 = 0;
    __block NSInteger sum7 = 0;
    __block NSInteger sum8 = 0;
    __block NSInteger sum9 = 0;
    __block NSInteger sum10 = 0;
    __block NSInteger sumTotal = 0;

         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 1; j < 11; j ++) {
         sum1 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 11; j < 21; j ++) {
         sum2 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 21; j < 31; j ++) {
         sum3 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 31; j < 41; j ++) {
         sum4 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 41; j < 51; j ++) {
         sum5 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 51; j < 61; j ++) {
         sum6 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 61; j < 71; j ++) {
         sum7 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 71; j < 81; j ++) {
         sum8 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 81; j < 91; j ++) {
         sum9 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 91; j < 101; j ++) {
         sum10 += j;
         }
         
         });
         
         dispatch_group_notify(group, mainQ, ^{
         
         sumTotal = sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7 + sum8 + sum9 + sum10;
         NSLog(@"%li", sumTotal);
         });
    
  6. dispatch_barrier_async
    在訪問數據庫或文件時,使用串行隊列可避免數據競爭的問題。寫入處理確實不可與寫入處理以及讀取處理并行處理,但是如果讀取處理與讀取處理并行,也不會出問題,為了高效進行訪問,讀取處理追加到并行隊列,寫入處理在沒有讀取處理進行的情況下追加到串行隊列。這時候可以使用此函數。
    示例代碼如下:
    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    for (NSInteger i = 0; i < 10; i ++) {

             dispatch_barrier_async(queue, ^{
             [NSThread sleepForTimeInterval:1];
             NSLog(@"reader... %li", i);
             
             });
             }
             
             dispatch_barrier_async(queue, ^{
             [NSThread sleepForTimeInterval:1];
             NSLog(@"writer...");
             
             });
             
             for (NSInteger i = 10; i < 20; i ++) {
             
             dispatch_barrier_async(queue, ^{
             [NSThread sleepForTimeInterval:1];
             NSLog(@"reader...%li", i);
             
             });
             }
             NSLog(@"---------------");
    
  7. dispatch_suspend dispatch_resume
    當追加大量處理到隊列時,在追加處理的過程中,有時希望不執行已追加的處理。這種情況下,只要掛起隊列即可,當可以執行再恢復。
    dispatch_suspend(queue);
    dispatch_resume(queue);
    這倆函數對已經執行的處理沒有影響,掛起后,追加到隊列但尚未運行的處理會停止執行。而恢復則使得這些處理能夠繼續執行。

  8. dispatch_semaphore
    當并行的處理更新數據時,會產生數據不一樣的情況,有時應用程序還會異常結束,此函數可以用來控制線程互斥訪問共享資源。如下示例代碼
    NSMutableArray *arrayM = [NSMutableArray array];
    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);

     for (NSInteger i = 0; i < 1000; i ++) {
     dispatch_async(queue, ^{
        
     dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
     
     [arrayM  addObject:@(i)];
     
     dispatch_semaphore_signal(sem);
     
     });
     }
     }
    
  9. dispatch_once
    該函數是保證在應用程序中只執行一次制定處理的。
    static dispatch_once_t once;

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

推薦閱讀更多精彩內容

  • GCD (Grand Central Dispatch) :iOS4 開始引入,使用更加方便,程序員只需要將任務添...
    池鵬程閱讀 1,343評論 0 2
  • 1. GCD簡介 什么是GCD呢?我們先來看看百度百科的解釋簡單了解下概念 引自百度百科:Grand Centra...
    千尋_544f閱讀 383評論 0 0
  • Demo地址 一 概念 全稱是Grand Central Dispatch,中樞調度器 純C語言,提供了非常多強大...
    Mitchell閱讀 663評論 0 4
  • 多線程 在iOS開發中為提高程序的運行效率會將比較耗時的操作放在子線程中執行,iOS系統進程默認啟動一個主線程,用...
    郭豪豪閱讀 2,608評論 0 4
  • 前言 在學習Android編程的時候,我們經常會使用 runOnUiThread(),把UI相關的操作放到里面。而...
    ChenJZ閱讀 11,829評論 22 36