iOS多線程:GCD詳解

多線程開發是平常開發任務中不可缺乏的一部分,在iOS開發中經常使用到的多線程開發技術有GCD、NSOperation、NSThread,本文主要講解多線系列文章中關於NSOperation的相關知識和使用詳解。html

  1. iOS多線程:GCD詳解
  2. iOS多線程:NSOperation詳解

一、GCD簡介

GCD對於iOS開發者來講並不陌生,在實際開發中咱們會常常用到GCD進行多線程的處理,那麼GCD是什麼呢?編程

Grand Central Dispatch(GCD) 是 Apple 開發的一個多核編程的較新的解決方法。它主要用於優化應用程序以支持多核處理器以及其餘對稱多處理系統。它是一個在線程池模式的基礎上執行的併發任務。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。安全

GCD有着很明顯的優點,正是這些優點才使得GCD在處理多線程問題有着舉足輕重的地位。bash

  1. GCD是apple爲多核的並行運算提出的解決方案。
  2. GCD能較好的利用CPU內核資源。
  3. GCD不須要開發者去管理線程的生命週期。
  4. 使用簡便,開發者只須要告訴GCD執行什麼任務,並不須要編寫任何線程管理代碼。

二、GCD任務和隊列

相信不少初級開發者會對GCD任務和隊列之間的關係理解含糊不清,實際上隊列只是提供了保存任務的容器。爲了更好的理解GCD,頗有必要先了解任務隊列的概念。網絡

2.1 GCD任務

任務就是須要執行的操做,是GCD中放在block中在線程中執行的那段代碼。任務的執行的方式有同步執行異步執行兩中執行方式。二者的主要區別是是否等待隊列的任務執行結束,以及是否具有開啓新線程的能力多線程

  • 同步執行(sync):同步添加任務到隊列中,在隊列以前的任務執行結束以前會一直等待;同步執行的任務只能在當前線程中執行,不具有開啓新線程的能力。
  • 異步執行(async):異步添加任務到隊列中,並須要理會隊列中其餘的任務,添加即執行;異步執行能夠在新的線程中執行,具有開啓新的線程的能力。

2.2 GCD的隊列

隊列:隊列是一種特殊的線性表,隊列中容許插入操做的一端稱爲隊尾,容許刪除操做的一端稱爲隊頭,是一種先進先出的結構。在GCD裏面隊列是指執行任務的等待隊列,是用來存聽任務的。按照隊列的結構特性,新任務老是插入在隊列的末尾,而任務的執行老是從隊列的對頭輸出,每讀取一個任務,則從隊列中釋放一個任務。GCD的隊列分爲串行隊列併發隊列兩種,二者都符合 FIFO(先進先出)的原則。二者的主要區別是:執行順序不一樣,以及開啓線程數不一樣。併發

  • 串行隊列:只開啓一個線程,每次只能有一個任務執行,等待執行完畢後纔會執行下一個任務。
  • 併發隊列:可讓對個任務同時執行,也就是開啓多個線程,讓多個任務同時執行。

二者之間區別以下圖所示: app

串行隊列

併發隊列

三、GCD基本使用

GCD的使用很簡單,首先建立一個隊列,而後向隊列中追加任務,系統會根據任務的類型執行任務。異步

3.一、隊列的建立

  1. 隊列的建立很簡單,只須要調用dispatch_queue_create方法傳入相對應的參數即可。這個方法有兩個參數:
  • 第一個參數表示隊列的惟一標識,能夠傳空。
  • 第二個參數用來識別是串行隊列仍是併發隊列。DISPATCH_QUEUE_SERIAL 表示串行隊列,DISPATCH_QUEUE_CONCURRENT表示併發隊列。
// 建立串行隊列
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
// 建立併發隊列
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
複製代碼
  1. GCD默認提供一種全局併發隊列,調用 dispatch_get_global_queue方法來獲取全局併發隊列。這個方法須要傳入兩個參數。
  • 第一個參數是一個長整型類型的參數,表示隊列優先級,有DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUNDDISPATCH_QUEUE_PRIORITY_DEFAULT四個選項,通常用 DISPATCH_QUEUE_PRIORITY_DEFAULT
  • 第二個參數暫時沒用,用 0 便可。
// 獲取全局併發隊列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
複製代碼
  1. GCD默認提供了主隊列,調用dispatch_get_main_queue方法獲取,全部放在主隊列中的任務都會在主線程中執行。主隊列是一種串行隊列。
// 主隊
dispatch_queue_t mainQueue = dispatch_get_main_queue();
複製代碼

3.二、建立任務

GCD調用dispatch_sync建立同步任務,調用dispatch_async建立異步任務。任務的內容都是在block代碼塊中。async

//異步任務
dispatch_async(queue, ^{
   //異步執行的代碼
});
 
//同步任務   
dispatch_sync(queue, ^{
   //同步執行的代碼
});
複製代碼

3.三、任務和隊列的組合

建立的任務須要放在隊列中去執行,同時考慮到主隊列的特殊性,那麼在不考慮嵌套任務的狀況下就會存在同步任務+串行隊列、同步任務+併發隊列、異步任務+串行隊列、異步任務+併發隊列、主隊列+同步任務、主隊列+異步任務六種組合,下面咱們來分析下這幾種組合。

  • 同步任務+串行隊列:同步任務不會開啓新的線程,任務串行執行。
  • 同步任務+併發隊列:同步任務不會開啓新的線程,雖然任務在併發隊列中,可是系統只默認開啓了一個主線程,沒有開啓子線程,因此任務串行執行。
  • 異步任務+串行隊列:異步任務有開啓新的線程,任務串行執行。
  • 異步任務+併發隊列:異步任務有開啓新的線程,任務併發執行。
  • 主隊列+同步任務:主隊列是一種串行隊列,任務在主線程中串行執行,將同步任務添加到主隊列中會形成追加的同步任務和主線程中的任務相互等待阻塞主線程,致使死鎖。
  • 主隊列+異步任務:主隊列是一種串行隊列,任務在主線程中串行執行,即便是追加的異步任務也不會開啓新的線程,任務串行執行。

下面咱們來看看各類組合之間的使用。

3.四、GCD的基礎使用

3.4.一、同步任務+串行隊列
- (void)syncTaskWithSerial {
    NSLog(@"currentThread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"currentThread-1:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"currentThread-2:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"currentThread-3:%@", [NSThread currentThread]);
    });
}
複製代碼

打印結果: 2020-03-12 21:34:25.807965+0800 ThreadDemo[51144:6948582] currentThread:<NSThread: 0x600001739100>{number = 1, name = main} 2020-03-12 21:34:25.808231+0800 ThreadDemo[51144:6948582] currentThread-1:<NSThread: 0x600001739100>{number = 1, name = main} 2020-03-12 21:34:25.808467+0800 ThreadDemo[51144:6948582] currentThread-2:<NSThread: 0x600001739100>{number = 1, name = main} 2020-03-12 21:34:25.808669+0800 ThreadDemo[51144:6948582] currentThread-3:<NSThread: 0x600001739100>{number = 1, name = main}

從上面代碼運行的結果能夠看出,並無開啓新的線程,任務是按順序執行的。

3.4.二、同步任務+併發隊列
- (void)syncTaskWithConcurrent {
    NSLog(@"current thread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        NSLog(@"current thread-1:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"current thread-2:%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"current thread-3:%@", [NSThread currentThread]);
    });
}
複製代碼

打印結果: 2020-03-12 21:39:45.931001+0800 ThreadDemo[51225:6953218] current thread:<NSThread: 0x600002abe0c0>{number = 1, name = main} 2020-03-12 21:39:45.931259+0800 ThreadDemo[51225:6953218] current thread-1:<NSThread: 0x600002abe0c0>{number = 1, name = main} 2020-03-12 21:39:45.931442+0800 ThreadDemo[51225:6953218] current thread-2:<NSThread: 0x600002abe0c0>{number = 1, name = main} 2020-03-12 21:39:45.931606+0800 ThreadDemo[51225:6953218] current thread-3:<NSThread: 0x600002abe0c0>{number = 1, name = main}

從上面代碼運行的結果能夠看出,同步任務不會開啓新的線程,雖然任務在併發隊列中,可是系統只默認開啓了一個主線程,沒有開啓子線程,因此任務串行執行。

3.4.三、異步任務+串行隊列
- (void)asyncTaskWithSeria{
    NSLog(@"current thread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"current thread-1:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-2:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-3:%@", [NSThread currentThread]);
    });
    NSLog(@"4");
}
複製代碼

打印結果: 2020-03-12 21:44:22.369058+0800 ThreadDemo[51283:6957598] current thread:<NSThread: 0x6000031891c0>{number = 1, name = main} 2020-03-12 21:44:22.369279+0800 ThreadDemo[51283:6957598] 4 2020-03-12 21:44:22.369346+0800 ThreadDemo[51283:6958684] current thread-1:<NSThread: 0x6000031acb80>{number = 7, name = (null)} 2020-03-12 21:44:22.369511+0800 ThreadDemo[51283:6958684] current thread-2:<NSThread: 0x6000031acb80>{number = 7, name = (null)} 2020-03-12 21:44:22.369675+0800 ThreadDemo[51283:6958684] current thread-3:<NSThread: 0x6000031acb80>{number = 7, name = (null)}

從上面代碼運行的結果能夠看出,開啓了一個新的線程,說明異步任務具有開啓新的線程的能力,可是因爲任務是在串行隊列中執行的,因此任務是順序執行的。

3.4.四、異步任務+併發隊列
- (void)asyncTaskWithConcurrent{
    NSLog(@"current thread:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"current thread-1:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-2:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-3:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"current thread-4:%@", [NSThread currentThread]);
    });
}
複製代碼

打印結果: 2020-03-12 21:59:36.511770+0800 ThreadDemo[51635:6976397] current thread:<NSThread: 0x60000024ed00>{number = 1, name = main} 2020-03-12 21:59:36.512015+0800 ThreadDemo[51635:6976575] current thread-2:<NSThread: 0x600000214ec0>{number = 5, name = (null)} 2020-03-12 21:59:36.512011+0800 ThreadDemo[51635:6976577] current thread-1:<NSThread: 0x600000215700>{number = 4, name = (null)} 2020-03-12 21:59:36.512028+0800 ThreadDemo[51635:6976580] current thread-3:<NSThread: 0x60000021f2c0>{number = 6, name = (null)} 2020-03-12 21:59:36.512035+0800 ThreadDemo[51635:6976578] current thread-4:<NSThread: 0x60000023b340>{number = 7, name = (null)}

從上面代碼的運行結果能夠看出,生成了多個線程,而且任務是隨機執行(併發執行)的。

3.4.五、主隊列+同步任務
-(void)syncTaskWithMain{
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });
    
    NSLog(@"4");
}
複製代碼

打印結果: 2020-03-12 22:05:01.689594+0800 ThreadDemo[51754:6982402] currentThread---<NSThread: 0x600003eaed00>{number = 1, name = main} (lldb)

很明顯上面這段代碼運行崩潰了,這是由於咱們在主線程中執行 syncTaskWithMain 方法,至關於把 syncTaskWithMain 任務放到了主線程的隊列中。而 同步執行會等待當前隊列中的任務執行完畢,纔會接着執行。那麼當咱們把 任務 1 追加到主隊列中,任務 1 就在等待主線程處理完 syncTaskWithMain 任務。而syncMain 任務須要等待任務 1 執行完畢,這樣就造成了相互等待的狀況,產生了死鎖。

3.4.六、主隊列+異步任務
-(void)asyncTaskWithMain{
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
    });
    
    NSLog(@"4");
}
複製代碼

打印結果: 2020-03-12 22:09:49.285203+0800 ThreadDemo[51832:6986908] currentThread---<NSThread: 0x600000aff3c0>{number = 1, name = main} 2020-03-12 22:09:49.285539+0800 ThreadDemo[51832:6986908] 4 2020-03-12 22:09:49.326310+0800 ThreadDemo[51832:6986908] 1---<NSThread: 0x600000aff3c0>{number = 1, name = main} 2020-03-12 22:09:49.326749+0800 ThreadDemo[51832:6986908] 2---<NSThread: 0x600000aff3c0>{number = 1, name = main} 2020-03-12 22:09:49.326988+0800 ThreadDemo[51832:6986908] 3---<NSThread: 0x600000aff3c0>{number = 1, name = main}

從上面代碼的運行結果能夠看出,雖然是異步任務,可是並無開啓新的線程,任然是在主線程中執行,而且任務是順序執行的。

3.五、任務嵌套使用

關於任務的嵌套使用有多種狀況,這裏作一個簡單的總結,

區別 『異步執行+併發隊列』嵌套『同一個併發隊列』 『同步執行+併發隊列』嵌套『同一個併發隊列』 『異步執行+串行隊列』嵌套『同一個串行隊列』 『同步執行+串行隊列』嵌套『同一個串行隊列』
同步 沒有開啓新的線程,串行執行任務 沒有開啓新線程,串行執行任務 死鎖卡住不執行 死鎖卡住不執行
異步 有開啓新線程,併發執行任務 有開啓新線程,併發執行任務 有開啓新線程(1 條),串行執行任務 有開啓新線程(1 條),串行執行任務

對於**『異步執行+串行隊列』嵌套『同一個串行隊列』**形成死鎖的狀況請看以下代碼:

dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
//異步任務A
dispatch_async(queue, ^{   
    //同步任務B
    dispatch_sync(queue, ^{  
        NSLog(@"任務C---%@",[NSThread currentThread]);
    });
});
複製代碼

首先異步任務A進入到隊列中,同步任務B對於異步任務A來講是代碼執行部分,C對於同步任務B來講是代碼執行部分,由於是在串行隊列中,任務是串行執行的,根據隊列先進先出原則,首先須要把任務A取出執行,即執行B的部分,可是B依賴C的執行,而C等待着B執行完成後執行,這樣就造成了一個相互等待,形成死鎖卡死。

**同步執行+串行隊列』嵌套『同一個串行隊列』**形成死鎖的狀況同理分析。

四、GCD線程間的通訊

在 iOS 開發過程當中,咱們在主線程進行UI刷新,把圖片下載、文件上傳、網絡請求等一些耗時的操做放在其餘的線程,當這些耗時的操做完成後須要將數據同步給UI,就須要回到主線程刷新UI,那麼就要用到線程之間的通信。GCD提供了很是簡便的方法進行線程間的通信。

- (void)communication {
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
        // 模擬耗時操做
        sleep(2);
        // 回到主線程
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        });
    });
}
複製代碼

打印結果 2020-03-13 00:06:07.632014+0800 ThreadDemo[53480:7074047] 1---<NSThread: 0x60000063e480>{number = 5, name = (null)} 2020-03-13 00:06:09.633041+0800 ThreadDemo[53480:7073841] 2---<NSThread: 0x60000061a180>{number = 1, name = main}

從上面代碼運行的結果能夠看出,1是在子線程中執行的,隔2秒後打印2,2是在主線程中執行的。

五、GCD中的函數方法

GCD中提供了諸多的函數方法供開發者調用,咱們一塊兒來看下這些方法的使用。

5.一、柵欄方法

有的時候咱們須要異步執行兩組操做,等待第一組執行完成後纔回去執行第二組操做,這個時候柵欄方法就起做用了。 柵欄方法(dispatch_barrier_asyncdispatch_barrier_sync)會等前邊追加到隊列中的任務執行完畢後,再將制定的任務追加到隊列中,而後等到dispatch_barrier_asyncdispatch_barrier_sync方法追加的任務執行完畢後纔會去執行後邊追加到隊列中的任務,簡單來講dispatch_barrier_asyncdispatch_barrier_sync將異步任務分紅了兩個組,執行完第一組後,再執行本身,而後執行隊列中剩餘的任務。惟一不一樣的是dispatch_barrier_async不會阻塞線程。

看以下代碼:

- (void)barrierTask {
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"start");
    dispatch_async(queue, ^{
        NSLog(@"currentThread-1:%@", [NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"currentThread-2:%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2];
    });
    NSLog(@"pause");
    dispatch_async(queue, ^{
        NSLog(@"currentThread-3:%@", [NSThread currentThread]);
    });
    NSLog(@"end");
}
複製代碼

打印結果 2020-03-13 00:31:53.247514+0800 ThreadDemo[54101:7100220] start 2020-03-13 00:31:53.247730+0800 ThreadDemo[54101:7100220] pause 2020-03-13 00:31:53.247809+0800 ThreadDemo[54101:7100396] currentThread-1:<NSThread: 0x600003b8db00>{number = 5, name = (null)} 2020-03-13 00:31:53.247883+0800 ThreadDemo[54101:7100220] end 2020-03-13 00:31:53.247991+0800 ThreadDemo[54101:7100396] currentThread-2:<NSThread: 0x600003b8db00>{number = 5, name = (null)} 2020-03-13 00:31:55.250622+0800 ThreadDemo[54101:7100396] currentThread-3:<NSThread: 0x600003b8db00>{number = 5, name = (null)}

從上面的代碼運行結果能夠看出,start、pause、end都是在2執行答應的,說明dispatch_barrier_async並無阻塞線程,3是在2打印兩秒後打印的。

若是把dispatch_barrier_async換成dispatch_barrier_sync打印結果會是怎麼樣呢?

- (void)barrierTask {
    dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"start");
    dispatch_async(queue, ^{
        NSLog(@"currentThread-1:%@", [NSThread currentThread]);
    });
    dispatch_barrier_sync(queue, ^{
        NSLog(@"currentThread-2:%@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2];
    });
    NSLog(@"pause");
    dispatch_async(queue, ^{
        NSLog(@"currentThread-3:%@", [NSThread currentThread]);
    });
    NSLog(@"end");
}
複製代碼

打印結果 2020-03-13 00:35:01.460109+0800 ThreadDemo[54171:7103212] start 2020-03-13 00:35:01.460408+0800 ThreadDemo[54171:7103379] currentThread-1:<NSThread: 0x600002508540>{number = 6, name = (null)} 2020-03-13 00:35:01.460588+0800 ThreadDemo[54171:7103212] currentThread-2:<NSThread: 0x600002570880>{number = 1, name = main} 2020-03-13 00:35:03.461678+0800 ThreadDemo[54171:7103212] pause 2020-03-13 00:35:03.462012+0800 ThreadDemo[54171:7103212] end 2020-03-13 00:35:03.462145+0800 ThreadDemo[54171:7103379] currentThread-3:<NSThread: 0x600002508540>{number = 6, name = (null)}

從上面代碼運行的結果能夠看出,pause和end是在2以後打印的,說明dispatch_barrier_sync阻塞了線程,須要等待dispatch_barrier_sync執行完成後纔會日後執行。

5.二、延時執行方法-dispatch_after

延時執行任務相信對於iOS開發者來講並不陌生,咱們常常遇到須要在延後指定之間後執行某個操做的需求,那麼這種需求用GCD來實現是很方便的。GCD的延時執行的函數是dispatch_after。須要注意的是:dispatch_after 方法並非在指定時間以後纔開始執行處理,而是在指定時間以後將任務追加到主隊列中。嚴格來講,這個時間並非絕對準確的,但想要大體延遲執行任務,dispatch_after 方法是頗有效的。

-(void)afterTask{
    NSLog(@"begin");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"after---%@",[NSThread currentThread]);
    });
}
複製代碼

打印結果 2020-03-13 00:43:47.787769+0800 ThreadDemo[54378:7111012] begin 2020-03-13 00:43:50.788086+0800 ThreadDemo[54378:7111012] after---<NSThread: 0x60000042ddc0>{number = 1, name = main}

從上面代碼的運行結果能夠看出afer是在begin打印後3秒纔打印的。

5.三、一次性代碼-dispatch_once

GCD提供了只執行一次的方法dispatch_once,這個方法在咱們建立單例的時候回常常用到。dispatch_once方法能夠保證一段代碼在程序運行過程當中只被調用一次,並且在多線程環境下能夠保證線程安全。

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

5.五、調度組

調度組簡單來講就是把異步執行的任務進行分組,等待全部的分組任務都執行完畢後再回到指定的線程執行任務。調用組使用dispatch_group_create來建立一個分組,dispatch_group_async方法先把任務添加到隊列中,而後將隊列方到調度組中,或者也可使用dispatch_group_enterdispatch_group_leave捉對實現將隊列添加到調度組。調用dispatch_group_notify方法回到指定線程執行任務,或者調用dispatch_group_wait阻塞當前線程。

- (void)groupNotifyTest{
    NSLog(@"current thread:%@", [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread-1:%@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread-2:%@", [NSThread currentThread]);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread-3:%@", [NSThread currentThread]);
        NSLog(@"group-end");
    });

    //會阻塞線程,
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"dispatch_group_wait後繼續執行任務");
}
複製代碼

打印結果: 2020-03-14 23:58:09.871023+0800 ThreadDemo[77370:8259501] current thread:<NSThread: 0x6000019fe480>{number = 1, name = main} 2020-03-14 23:58:11.874345+0800 ThreadDemo[77370:8260290] thread-2:<NSThread: 0x600001996e80>{number = 7, name = (null)} 2020-03-14 23:58:11.874343+0800 ThreadDemo[77370:8259684] thread-1:<NSThread: 0x6000019a55c0>{number = 5, name = (null)} 2020-03-14 23:58:11.874672+0800 ThreadDemo[77370:8259501] dispatch_group_wait後繼續執行任務 2020-03-14 23:58:13.877077+0800 ThreadDemo[77370:8259501] thread-3:<NSThread: 0x6000019fe480>{number = 1, name = main} 2020-03-14 23:58:13.877365+0800 ThreadDemo[77370:8259501] group-end

在這裏須要說明的一點是dispatch_group_wait,該方法須要傳入兩個參數,第一個參數是group即調度組,第二個參數是timerout即指定等待的時間。一旦調用dispatch_group_wait函數,該函數就處理調用的狀態而不返回值,只有當函數的currentThread中止,或到達wait函數指定的等待的時間,或Dispatch Group中的操做所有執行完畢以前,執行該函數的線程中止。當指定timeout爲DISPATCH_TIME_FOREVER時就意味着永久等待;當指定timeout爲DISPATCH_TIME_NOW時就意味不用任何等待便可斷定屬於Dispatch Group的處理是否所有執行結束。若是dispatch_group_wait函數返回值不爲0,就意味着雖然通過了指定的時間,但Dispatch Group中的操做並未所有執行完畢。若是dispatch_group_wait函數返回值爲0,就意味着Dispatch Group中的操做所有執行完畢。

下面看dispatch_group_enterdispatch_group_leave捉對實現將隊列添加到調度組的狀況。dispatch_group_enter 標誌着在group的任務數+1,dispatch_group_leave 標誌着在group中的任務數-1,表示已經完成了一個任務。

- (void)groupEnterTest {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_enter(group);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread_1:%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread_2:%@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"thread_3:%@", [NSThread currentThread]);
        NSLog(@"group_end");
    });
}
複製代碼

打印結果: 2020-03-15 00:07:17.293333+0800 ThreadDemo[77525:8269621] thread_2:<NSThread: 0x600002434ac0>{number = 7, name = (null)} 2020-03-15 00:07:17.293320+0800 ThreadDemo[77525:8269427] thread_1:<NSThread: 0x60000241b9c0>{number = 3, name = (null)} 2020-03-15 00:07:19.294186+0800 ThreadDemo[77525:8269235] thread_3:<NSThread: 0x600002450b80>{number = 1, name = main} 2020-03-15 00:07:19.294485+0800 ThreadDemo[77525:8269235] group_end

須要注意的是dispatch_group_enterdispatch_group_leave捉對出現的。

5.六、信號量

GCD中的信號量是指的Dispatch Semaphore,是持有計數的信號。當信號量小於0時就會一直等待即阻塞所在線程,不然就能夠正常執行。信號量能夠保持線程的同步,將異步執行任務轉換成同步任務執行, 同時保持線程的安全

Dispatch Semaphore提供了三個方法:

  • dispatch_semaphore_create:建立一個 Semaphore 並初始化信號的總量
  • dispatch_semaphore_signal:發送一個信號,讓信號總量加 1
  • dispatch_semaphore_wait:可使總信號量減 1,信號總量小於 0 時就會一直等待(阻塞所在線程),不然就能夠正常執行。
- (void)semaphoreTest {
   dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    __block int a = 0;
    while (a < 5) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"裏面的a的值:%d-----%@", a, [NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
            a++;
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    }
    NSLog(@"外面的a的值:%d", a);
}
複製代碼

打印結果: 2020-03-15 00:44:53.005899+0800 ThreadDemo[78303:8318841] 裏面的a的值:0-----<NSThread: 0x600003c222c0>{number = 3, name = (null)} 2020-03-15 00:44:53.006161+0800 ThreadDemo[78303:8318841] 裏面的a的值:1-----<NSThread: 0x600003c222c0>{number = 3, name = (null)} 2020-03-15 00:44:53.006354+0800 ThreadDemo[78303:8318841] 裏面的a的值:2-----<NSThread: 0x600003c222c0>{number = 3, name = (null)} 2020-03-15 00:44:53.006551+0800 ThreadDemo[78303:8318841] 裏面的a的值:3-----<NSThread: 0x600003c222c0>{number = 3, name = (null)} 2020-03-15 00:44:53.006727+0800 ThreadDemo[78303:8318841] 裏面的a的值:4-----<NSThread: 0x600003c222c0>{number = 3, name = (null)} 2020-03-15 00:44:53.006862+0800 ThreadDemo[78303:8318672] 外面的a的值:5

5.七、調度源-Dispatch_source

調度源是協調特殊低級別系統事件處理的基本數據類型。GCD支持諸如定時器調度源、信號調度源、描述符調度源、進程調度源、端口調度源、自定義調度源等。調度源有這一系列的成熟API,在這裏就很少作描述,詳細能夠查閱官方的文檔Dispatch Sources

相關文章
相關標籤/搜索