iOS GCD (Grand Central Dispatch)

1. GCD簡(jiǎn)介

百度百科給出的定義是:

Grand Central Dispatch (GCD)是Apple開發(fā)的一個(gè)多核編程的較新的解決方法。它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對(duì)稱多處理系統(tǒng)。它是一個(gè)在線程池模式的基礎(chǔ)上執(zhí)行的并行任務(wù)。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用

GCD是基于C語(yǔ)言的,他負(fù)責(zé)創(chuàng)建線程和調(diào)度需要執(zhí)行的任務(wù),由系統(tǒng)直接提供線程管理。GCD有兩個(gè)核心的概念:隊(duì)列任務(wù)。

1.1 隊(duì)列

隊(duì)列就是一個(gè)用來(lái)存放任務(wù)的集合,負(fù)責(zé)管理開發(fā)者提交的任務(wù)。系統(tǒng)會(huì)負(fù)責(zé)管理這些隊(duì)列,并放到多個(gè)線程上執(zhí)行。無(wú)須開發(fā)者直接管理。

隊(duì)列會(huì)維護(hù)和使用一個(gè)線程池來(lái)處理用戶提交的任務(wù),線程池的作用就是執(zhí)行隊(duì)列管理的任務(wù)。GCD的隊(duì)列嚴(yán)格遵守FIFO(先進(jìn)先出)的工作原則。添加到隊(duì)列的工作單元將始終按照隊(duì)列的啟動(dòng)順序啟動(dòng)。

根據(jù)執(zhí)行任務(wù)的不同,隊(duì)列分為并發(fā)隊(duì)列串行隊(duì)列

  • 串行隊(duì)列(Serial Dispatch Queue)
    串行隊(duì)列底層的線程池只有一個(gè)線程,一次只能運(yùn)行一個(gè)任務(wù),前一個(gè)任務(wù)執(zhí)行完成才能繼續(xù)執(zhí)行下一個(gè)任務(wù)。

  • 并發(fā)隊(duì)列(Concurrent Dispatch Queue)
    串行隊(duì)列底層的線程池提供了多個(gè)線程,可以按照FIFO的順序并發(fā)啟動(dòng)、執(zhí)行多個(gè)任務(wù)

1.2 任務(wù)

任務(wù)就是我們提交給隊(duì)列的工作單元,也就是你在線程中執(zhí)行的代碼塊。執(zhí)行任務(wù)的方式有兩種:同步執(zhí)行異步執(zhí)行。兩者的主要區(qū)別就是:隊(duì)列是否需要等待其他任務(wù)執(zhí)行結(jié)束以及是否具備開啟新線程的能力。

  • 同步執(zhí)行(sync)
    同步執(zhí)行就是只會(huì)在當(dāng)前線程執(zhí)行,不具備開啟新線程的能力。開發(fā)者同步提交任務(wù),可以避免競(jìng)爭(zhēng)條件和其他同步錯(cuò)誤。但是需要注意的是,同步提交任務(wù)會(huì)阻塞當(dāng)前調(diào)用的線程,直到相應(yīng)的任務(wù)執(zhí)行完成。同步提交任務(wù)定義的兩個(gè)函數(shù)如下:
///queue:將任務(wù)提交到的隊(duì)列  block:要執(zhí)行的任務(wù)
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
///queue:將任務(wù)提交到的隊(duì)列  context:向函數(shù)傳入應(yīng)用程序定義的上下文  work:傳入的其他需要執(zhí)行的函數(shù)
void dispatch_sync_f(dispatch_queue_t queue,  void *_Nullable context, dispatch_function_t work);
  • 異步執(zhí)行(async)
    異步執(zhí)行就是會(huì)在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力。我們提交任務(wù)到隊(duì)列時(shí),無(wú)法確定什么時(shí)候能夠執(zhí)行(無(wú)需等待)。異步執(zhí)行任務(wù)的函數(shù)如下:
///queue:將任務(wù)提交到的隊(duì)列  block:要執(zhí)行的任務(wù)
void dispatch_async(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
///queue:將任務(wù)提交到的隊(duì)列  context:向函數(shù)傳入應(yīng)用程序定義的上下文  work:傳入的其他需要執(zhí)行的函數(shù)
void dispatch_async_f(dispatch_queue_t queue,  void *_Nullable context, dispatch_function_t work);

注意:異步執(zhí)行(async)雖然具有開啟新線程的能力,但是并不一定開啟新線程。這跟任務(wù)所指定的隊(duì)列類型有關(guān).

針對(duì)不同的隊(duì)列類型,同步和異步會(huì)產(chǎn)生不同的執(zhí)行結(jié)果,如下圖:

全局并行隊(duì)列 創(chuàng)建串行隊(duì)列 主隊(duì)列
同步(sync) 沒有開啟新線程,串行執(zhí)行任務(wù) 沒有開啟新線程,串行執(zhí)行任務(wù) 死鎖
異步(async) 有開啟新線程,并行執(zhí)行任務(wù) 有開啟新線程,串行執(zhí)行任務(wù) 沒有開啟新線程,串行執(zhí)行任務(wù)

總結(jié):同步和異步?jīng)Q定了是否開啟新線程,并發(fā)和串行決定了任務(wù)的執(zhí)行方式

2. GCD的創(chuàng)建方法

GCD的使用分為兩步

  • 創(chuàng)建一個(gè)隊(duì)列(串行或者并行)
  • 將任務(wù)提交到隊(duì)列中,系統(tǒng)會(huì)根據(jù)任務(wù)類型執(zhí)行任務(wù)(同步或者是異步執(zhí)行)

2.1 創(chuàng)建(獲?。╆?duì)列

在GCD中,我們既可以手動(dòng)創(chuàng)建一個(gè)新的隊(duì)列,也可以獲取系統(tǒng)為我們提供的隊(duì)列。

  • 手動(dòng)創(chuàng)建隊(duì)列
    我們可以使用dispatch_queue_create函數(shù)來(lái)手動(dòng)創(chuàng)建一個(gè)隊(duì)列
dispatch_queue_t dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr);

該函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)表示隊(duì)列的唯一標(biāo)示,用于在debug的時(shí)候查看??梢詾榭眨珼ispatch Queue 的名稱推薦使用應(yīng)用程序 ID 這種逆序全程域名;第二個(gè)參數(shù)用來(lái)識(shí)別是串行隊(duì)列還是并發(fā)隊(duì)列。
DISPATCH_QUEUE_SERIAL :串行隊(duì)列
DISPATCH_QUEUE_CONCURRENT :并發(fā)隊(duì)列

  • 獲取系統(tǒng)提供的隊(duì)列
    系統(tǒng)為我們提供了兩種隊(duì)列,一個(gè)串行隊(duì)列和一個(gè)并發(fā)隊(duì)列。如下:
    系統(tǒng)提供了一種特殊的串行隊(duì)列:主隊(duì)列(Main Dispatch Queue)。主隊(duì)列中的任務(wù)都會(huì)放到主線程中去執(zhí)行。獲取方式如下:
  ///主隊(duì)列獲取方法
  dispatch_queue_t queue = dispatch_get_main_queue();

GCD默認(rèn)提供了一個(gè)全局并發(fā)隊(duì)列(Global Dispatch Queue)

/// 全局并發(fā)隊(duì)列的獲取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

該函數(shù)需要傳入兩個(gè)參數(shù)。第一個(gè)參數(shù)表示隊(duì)列優(yōu)先級(jí),一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二個(gè)參數(shù)暫時(shí)沒用,用0即可

2.2 創(chuàng)建任務(wù)

在上面我們已經(jīng)講過,GCD的任務(wù)執(zhí)行方式有兩種,同步執(zhí)行異步執(zhí)行。對(duì)應(yīng)的系統(tǒng)提供了同步執(zhí)行的創(chuàng)建方法dispatch_sync,異步執(zhí)行的創(chuàng)建方法dispatch_async。

/// 同步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_sync(queue, ^{
    /// 這里放同步執(zhí)行任務(wù)代碼
});
/// 異步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_async(queue, ^{
    /// 這里放異步執(zhí)行任務(wù)代碼
});

綜上所述,我們可以了解到,我們有兩種隊(duì)列的創(chuàng)建方式串行隊(duì)列并發(fā)隊(duì)列,兩種任務(wù)的執(zhí)行方式同步執(zhí)行異步執(zhí)行。這樣我們就有了四種不同的組合:

1. 同步執(zhí)行 + 并發(fā)隊(duì)列 
2. 同步執(zhí)行 + 串行隊(duì)列 
3. 異步執(zhí)行 + 并發(fā)隊(duì)列  
4. 異步執(zhí)行 + 串行隊(duì)列

實(shí)際上我們還有系統(tǒng)我我們提供的兩種特殊隊(duì)列:全局并發(fā)隊(duì)列主隊(duì)列。全局并發(fā)隊(duì)列實(shí)際上我們可以把它理解成普通的并發(fā)隊(duì)列來(lái)使用。但是主隊(duì)列應(yīng)為在主線程上執(zhí)行,所以他有點(diǎn)特殊,這樣我們又多了兩種組合

5. 同步執(zhí)行 + 主隊(duì)列
6. 異步執(zhí)行 + 主隊(duì)列

3 GCD的使用

3.1 同步執(zhí)行 + 并發(fā)隊(duì)列

/**
 * 同步執(zhí)行 + 并發(fā)隊(duì)列
 * 特點(diǎn):在當(dāng)前線程中執(zhí)行任務(wù),不會(huì)開啟新線程,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)。
 * 同步執(zhí)行:不開啟新線程
 */
- (void)syncConcurrent {

    NSLog(@"syncConcurrent--- %@ ---begin",[NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        /// 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        /// 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        /// 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3---%@",[NSThread currentThread]);
        }
    });
    NSLog(@"syncConcurrent--- %@ ---end",[NSThread currentThread]);
}
///執(zhí)行結(jié)果
2018-11-08 10:37:50.949598+0800 iOS GCD[929:31034] syncConcurrent--- <NSThread: 0x604000066540>{number = 1, name = main} ---begin
2018-11-08 10:37:52.950297+0800 iOS GCD[929:31034] 1---<NSThread: 0x604000066540>{number = 1, name = main}
2018-11-08 10:37:54.951011+0800 iOS GCD[929:31034] 1---<NSThread: 0x604000066540>{number = 1, name = main}
2018-11-08 10:37:56.951856+0800 iOS GCD[929:31034] 2---<NSThread: 0x604000066540>{number = 1, name = main}
2018-11-08 10:37:58.952883+0800 iOS GCD[929:31034] 2---<NSThread: 0x604000066540>{number = 1, name = main}
2018-11-08 10:38:00.953523+0800 iOS GCD[929:31034] 3---<NSThread: 0x604000066540>{number = 1, name = main}
2018-11-08 10:38:02.953879+0800 iOS GCD[929:31034] 3---<NSThread: 0x604000066540>{number = 1, name = main}
2018-11-08 10:38:02.954061+0800 iOS GCD[929:31034] syncConcurrent--- <NSThread: 0x604000066540>{number = 1, name = main} ---end

從運(yùn)行結(jié)果中我們可以看到:

  • 任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行,沒有開啟新線程。(同步執(zhí)行不具備開啟新線程的能力)
  • 任務(wù)在begin和end之間執(zhí)行。(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)
  • 任務(wù)按順序執(zhí)行原因:雖然并發(fā)隊(duì)里可以開啟多個(gè)線程同時(shí)執(zhí)行任務(wù),但是創(chuàng)建的任務(wù)的同步任務(wù)。同步任務(wù)執(zhí)行不具備開啟新線程的能力,所以就不能并發(fā)執(zhí)行。也就是只能串行執(zhí)行。

3.2 同步執(zhí)行 + 串行隊(duì)列

/**
 * 同步執(zhí)行 + 串行隊(duì)列
 * 特點(diǎn):在當(dāng)前線程中執(zhí)行任務(wù),不會(huì)開啟新線程,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)。
 * 同步執(zhí)行:不開啟新線程
 */
- (void)syncConcurrent {

    NSLog(@"syncConcurrent--- %@ ---begin",[NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        /// 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        /// 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        /// 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3---%@",[NSThread currentThread]);
        }
    });
    NSLog(@"syncConcurrent--- %@ ---end",[NSThread currentThread]);
}
///執(zhí)行結(jié)果
2018-11-08 10:53:32.296206+0800 iOS GCD[963:37026] syncConcurrent--- <NSThread: 0x60400006bc40>{number = 1, name = main} ---begin
2018-11-08 10:53:34.296791+0800 iOS GCD[963:37026] 1---<NSThread: 0x60400006bc40>{number = 1, name = main}
2018-11-08 10:53:36.298250+0800 iOS GCD[963:37026] 1---<NSThread: 0x60400006bc40>{number = 1, name = main}
2018-11-08 10:53:38.299682+0800 iOS GCD[963:37026] 2---<NSThread: 0x60400006bc40>{number = 1, name = main}
2018-11-08 10:53:40.301198+0800 iOS GCD[963:37026] 2---<NSThread: 0x60400006bc40>{number = 1, name = main}
2018-11-08 10:53:42.302730+0800 iOS GCD[963:37026] 3---<NSThread: 0x60400006bc40>{number = 1, name = main}
2018-11-08 10:53:44.304199+0800 iOS GCD[963:37026] 3---<NSThread: 0x60400006bc40>{number = 1, name = main}
2018-11-08 10:53:44.304465+0800 iOS GCD[963:37026] syncConcurrent--- <NSThread: 0x60400006bc40>{number = 1, name = main} ---end

從運(yùn)行結(jié)果中我們可以看到:

  • 任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行,沒有開啟新線程。(同步執(zhí)行不具備開啟新線程的能力)
  • 任務(wù)在begin和end之間執(zhí)行。(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)
  • 任務(wù)按順序執(zhí)行原因:串行隊(duì)列本身就是任務(wù)就是按照順序執(zhí)行,再加上同步執(zhí)行,不具備開啟新線程的能力,所以任務(wù)就會(huì)在主線程串行執(zhí)行。

3.3 異步執(zhí)行 + 并發(fā)隊(duì)列

/**
 * 異步執(zhí)行 + 并發(fā)隊(duì)列
 * 特點(diǎn):開啟新線程,任務(wù)同時(shí)執(zhí)行。
 */
- (void)syncConcurrent {

    NSLog(@"syncConcurrent--- %@ ---begin",[NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        /// 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        /// 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        /// 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3---%@",[NSThread currentThread]);
        }
    });
    NSLog(@"syncConcurrent--- %@ ---end",[NSThread currentThread]);
}
///執(zhí)行結(jié)果
2018-11-08 10:57:50.012065+0800 iOS GCD[981:39765] syncConcurrent--- <NSThread: 0x60400007d5c0>{number = 1, name = main} ---begin
2018-11-08 10:57:50.012305+0800 iOS GCD[981:39765] syncConcurrent--- <NSThread: 0x60400007d5c0>{number = 1, name = main} ---end
2018-11-08 10:57:52.013674+0800 iOS GCD[981:39833] 2---<NSThread: 0x600000472d00>{number = 3, name = (null)}
2018-11-08 10:57:52.013674+0800 iOS GCD[981:39836] 1---<NSThread: 0x600000472d40>{number = 4, name = (null)}
2018-11-08 10:57:52.013674+0800 iOS GCD[981:39834] 3---<NSThread: 0x604000267dc0>{number = 5, name = (null)}
2018-11-08 10:57:54.019337+0800 iOS GCD[981:39833] 2---<NSThread: 0x600000472d00>{number = 3, name = (null)}
2018-11-08 10:57:54.019337+0800 iOS GCD[981:39836] 1---<NSThread: 0x600000472d40>{number = 4, name = (null)}
2018-11-08 10:57:54.019353+0800 iOS GCD[981:39834] 3---<NSThread: 0x604000267dc0>{number = 5, name = (null)}

從運(yùn)行結(jié)果中我們可以看到:

  • 任務(wù)在新線程中執(zhí)行,并且任務(wù)沒有等待,是同時(shí)執(zhí)行(異步執(zhí)行具有開啟新線程的能力,并發(fā)隊(duì)列可以開啟多個(gè)線程同時(shí)執(zhí)行)。

3.4 異步執(zhí)行 + 串行隊(duì)列

/**
 * 異步執(zhí)行 + 串行隊(duì)列
 * 特點(diǎn):開啟新線程,任務(wù)按順序執(zhí)行。
 */
- (void)syncConcurrent {

    NSLog(@"syncConcurrent--- %@ ---begin",[NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        /// 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        /// 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        /// 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3---%@",[NSThread currentThread]);
        }
    });
    NSLog(@"syncConcurrent--- %@ ---end",[NSThread currentThread]);
}
///執(zhí)行結(jié)果
2018-11-08 11:24:53.262898+0800 iOS GCD[646:11217] syncConcurrent--- <NSThread: 0x60000007bc80>{number = 1, name = main} ---begin
2018-11-08 11:24:53.263132+0800 iOS GCD[646:11217] syncConcurrent--- <NSThread: 0x60000007bc80>{number = 1, name = main} ---end
2018-11-08 11:24:55.266359+0800 iOS GCD[646:11395] 1---<NSThread: 0x600000460a00>{number = 3, name = (null)}
2018-11-08 11:24:57.270609+0800 iOS GCD[646:11395] 1---<NSThread: 0x600000460a00>{number = 3, name = (null)}
2018-11-08 11:24:59.275577+0800 iOS GCD[646:11395] 2---<NSThread: 0x600000460a00>{number = 3, name = (null)}
2018-11-08 11:25:01.281035+0800 iOS GCD[646:11395] 2---<NSThread: 0x600000460a00>{number = 3, name = (null)}
2018-11-08 11:25:03.282101+0800 iOS GCD[646:11395] 3---<NSThread: 0x600000460a00>{number = 3, name = (null)}
2018-11-08 11:25:05.287519+0800 iOS GCD[646:11395] 3---<NSThread: 0x600000460a00>{number = 3, name = (null)}

從運(yùn)行結(jié)果中我們可以看到:

  • 開起了一條新線程,(異步執(zhí)行允許開啟新線程,串行允許開啟一條線程)。
  • 所有任務(wù)是在打印的begin和end之后才開始執(zhí)行的(異步執(zhí)行不會(huì)做任何等待,可以繼續(xù)執(zhí)行任務(wù))。
  • 任務(wù)是按順序執(zhí)行的(串行任務(wù)按順序依次執(zhí)行)。

3.5 同步執(zhí)行 + 主隊(duì)列

同步執(zhí)行+主隊(duì)列會(huì)造成線程卡死。最終到時(shí)程序崩潰。具體原因分析如下:

  • 同步執(zhí)行不具備開啟新線程的能力,主隊(duì)列即串行隊(duì)列。
  • 我們創(chuàng)建隊(duì)列的代碼在主線程執(zhí)行,任務(wù)也在主線程執(zhí)行。
  • 主線程首先創(chuàng)建執(zhí)行我們創(chuàng)建GCD的代碼,在執(zhí)行到執(zhí)行任務(wù)模塊,等到任務(wù)執(zhí)行完成才會(huì)繼續(xù)執(zhí)行下面的代碼(任務(wù)依次執(zhí)行)。
  • 我們的任務(wù)也要在主線程執(zhí)行,目前主線程已經(jīng)有創(chuàng)建GCD的任務(wù)還沒有執(zhí)行完成,所以它會(huì)等到創(chuàng)建GCD的代碼執(zhí)行才會(huì)開始執(zhí)行任務(wù)。
  • 這樣就造成了兩個(gè)任務(wù)相互等待的情況。造成主線程卡死。

注意 :通過上面的分析,同步執(zhí)行 + 主隊(duì)列的情況下,我們?cè)谧泳€程執(zhí)行創(chuàng)建GCD的任務(wù),將任務(wù)提交到主隊(duì)列,這時(shí)不會(huì)造成卡死情況。因?yàn)闆]有相互等待的情況發(fā)生。(創(chuàng)建GCD的任務(wù)被提交到了子線程)。

3.6 異步執(zhí)行 + 主隊(duì)列

/**
 * 異步執(zhí)行 + 主隊(duì)列
 * 特點(diǎn):開啟新線程,任務(wù)按順序執(zhí)行。
 */
- (void)syncConcurrent {

    NSLog(@"syncConcurrent--- %@ ---begin",[NSThread currentThread]);
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        /// 追加任務(wù)1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        /// 追加任務(wù)2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        /// 追加任務(wù)3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3---%@",[NSThread currentThread]);
        }
    });
    NSLog(@"syncConcurrent--- %@ ---end",[NSThread currentThread]);
}
///執(zhí)行結(jié)果
2018-11-08 11:39:45.317169+0800 iOS GCD[689:16564] syncConcurrent--- <NSThread: 0x604000070040>{number = 1, name = main} ---begin
2018-11-08 11:39:45.317404+0800 iOS GCD[689:16564] syncConcurrent--- <NSThread: 0x604000070040>{number = 1, name = main} ---end
2018-11-08 11:39:47.322342+0800 iOS GCD[689:16564] 1---<NSThread: 0x604000070040>{number = 1, name = main}
2018-11-08 11:39:49.323635+0800 iOS GCD[689:16564] 1---<NSThread: 0x604000070040>{number = 1, name = main}
2018-11-08 11:39:51.325077+0800 iOS GCD[689:16564] 2---<NSThread: 0x604000070040>{number = 1, name = main}
2018-11-08 11:39:53.326577+0800 iOS GCD[689:16564] 2---<NSThread: 0x604000070040>{number = 1, name = main}
2018-11-08 11:39:55.328106+0800 iOS GCD[689:16564] 3---<NSThread: 0x604000070040>{number = 1, name = main}
2018-11-08 11:39:57.329529+0800 iOS GCD[689:16564] 3---<NSThread: 0x604000070040>{number = 1, name = main}

從運(yùn)行結(jié)果中我們可以看到:

  • 所有任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行的,并沒有開啟新的線程(雖然異步執(zhí)行具備開啟線程的能力,但因?yàn)槭侵麝?duì)列,所以所有任務(wù)都在主線程中)。
  • 所有任務(wù)是在打印的begin和end之后才開始執(zhí)行的(異步執(zhí)行不會(huì)做任何等待,可以繼續(xù)執(zhí)行任務(wù))。

4 GCD線程之間的通信

在日常的開發(fā)中,我們將一些比較耗時(shí)的任務(wù)放在子線程中執(zhí)行,在某些情況下,子線程任務(wù)執(zhí)行完成之后需要再次回到主線程刷新UI。這時(shí)候就用到了線程的通信。

///線程間通信
- (void)communication {
    /// 獲取全局并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        /// 異步追加任務(wù)
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
        }
        
        /// 回到主線程
        dispatch_async(dispatch_get_main_queue(), ^{
            /// 追加在主線程中執(zhí)行的任務(wù)
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@",[NSThread currentThread]);
        });
    });
}
///執(zhí)行結(jié)果
2018-11-08 13:59:58.159160+0800 iOS GCD[902:52144] 1---<NSThread: 0x60400007b480>{number = 3, name = (null)}
2018-11-08 14:00:00.162415+0800 iOS GCD[902:52144] 1---<NSThread: 0x60400007b480>{number = 3, name = (null)}
2018-11-08 14:00:02.163904+0800 iOS GCD[902:52067] 2---<NSThread: 0x600000070900>{number = 1, name = main}

5 GCD 之 dispatch_once

dispatch_once函數(shù)是保證在應(yīng)用程序執(zhí)行中只執(zhí)行一次處理的API。即使是在多線程環(huán)境下,該函數(shù)也可以保證線程安全。

- (void)dispatchOnce {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        ///執(zhí)行代碼
    });
}

6 GCD 之 dispatch_apply

dispatch_apply函數(shù)是dispatch_sync函數(shù)和Dispatch Group關(guān)聯(lián)的API。該函數(shù)按指定的次數(shù)將指定的block追加到指定的Dispath Queue中,并等待全部處理執(zhí)行結(jié)束。

void dispatch_apply(size_t iterations, dispatch_queue_t queue, DISPATCH_NOESCAPE void (^block)(size_t));

第一參數(shù)為循環(huán)的次數(shù),第二個(gè)參數(shù)為循環(huán)隊(duì)列。如果為并發(fā)隊(duì)列。則循環(huán)異步執(zhí)行。如果是串行隊(duì)列,則任務(wù)同步執(zhí)行。以下為并發(fā)隊(duì)列演示代碼:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"apply---begin");
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zd  ---   %@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
///執(zhí)行結(jié)果
2018-11-08 17:38:10.029500+0800 iOS GCD[2056:148014] apply---begin
2018-11-08 17:38:10.029790+0800 iOS GCD[2056:148058] 1  ---   <NSThread: 0x6040004603c0>{number = 3, name = (null)}
2018-11-08 17:38:10.029788+0800 iOS GCD[2056:148014] 0  ---   <NSThread: 0x6000002616c0>{number = 1, name = main}
2018-11-08 17:38:10.029837+0800 iOS GCD[2056:148056] 3  ---   <NSThread: 0x60000046cb00>{number = 4, name = (null)}
2018-11-08 17:38:10.029855+0800 iOS GCD[2056:148055] 2  ---   <NSThread: 0x6040004604c0>{number = 5, name = (null)}
2018-11-08 17:38:10.029979+0800 iOS GCD[2056:148058] 4  ---   <NSThread: 0x6040004603c0>{number = 3, name = (null)}
2018-11-08 17:38:10.030072+0800 iOS GCD[2056:148014] 5  ---   <NSThread: 0x6000002616c0>{number = 1, name = main}
2018-11-08 17:38:10.030189+0800 iOS GCD[2056:148055] 7  ---   <NSThread: 0x6040004604c0>{number = 5, name = (null)}
2018-11-08 17:38:10.030189+0800 iOS GCD[2056:148056] 6  ---   <NSThread: 0x60000046cb00>{number = 4, name = (null)}
2018-11-08 17:38:10.030235+0800 iOS GCD[2056:148058] 8  ---   <NSThread: 0x6040004603c0>{number = 3, name = (null)}
2018-11-08 17:38:10.030328+0800 iOS GCD[2056:148014] 9  ---   <NSThread: 0x6000002616c0>{number = 1, name = main}
2018-11-08 17:38:10.031784+0800 iOS GCD[2056:148014] apply---end

7 GCD 之 dispatch_semaphore(信號(hào)量)

GCD中的Dispatch Semaphore是持有計(jì)數(shù)的信號(hào)。就是一種可用來(lái)控制訪問資源的數(shù)量的標(biāo)識(shí),設(shè)定了一個(gè)信號(hào)量,在線程訪問之前,加上信號(hào)量的處理,則可告知系統(tǒng)按照我們指定的信號(hào)量數(shù)量來(lái)執(zhí)行多個(gè)線程。在 Dispatch Semaphore 中,計(jì)數(shù)為0時(shí)等待,不可通過。計(jì)數(shù)為1或大于1時(shí),計(jì)數(shù)減1且不等待,可通過。

其實(shí),這有點(diǎn)類似鎖機(jī)制了,只不過信號(hào)量都是系統(tǒng)幫助我們處理了,我們只需要在執(zhí)行線程之前,設(shè)定一個(gè)信號(hào)量值,并且在使用時(shí),加上信號(hào)量處理方法就行了。

舉個(gè)例子:高速公路收費(fèi)站有兩個(gè)通過口,所以最多可以容納兩輛車同時(shí)通過,但是這時(shí)候來(lái)了三輛車過了,我們就可以控制先來(lái)的兩輛車先過,第三輛等待,等前面有一輛車通過了第三輛車再走。

Dispatch Semaphore 提供了三個(gè)函數(shù)。

  • dispatch_semaphore_create:創(chuàng)建一個(gè)Semaphore并初始化信號(hào)的總量
  • dispatch_semaphore_signal:發(fā)送一個(gè)信號(hào),讓信號(hào)總量加1
  • dispatch_semaphore_wait:可以使總信號(hào)量減1,當(dāng)信號(hào)總量為0時(shí)就會(huì)一直等待(阻塞所在線程),否則就可以正常執(zhí)行。

下面我們按照剛才舉得這個(gè)例子為例,看看代碼如何實(shí)現(xiàn):

- (void)dispatchSemaphore {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        ///信號(hào)總量減一 (可以理解為當(dāng)前任務(wù)占用一個(gè)收費(fèi)口,還剩一個(gè)收費(fèi)口)
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"第1輛車 進(jìn)入 收費(fèi)站1號(hào)窗口");
        sleep(1);
        NSLog(@"第1輛車 駛出 收費(fèi)站1號(hào)窗口");
        ///信號(hào)總量加一 (繼續(xù)保持2個(gè)收費(fèi)口數(shù)量)
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"第2輛車 進(jìn)入 收費(fèi)站1號(hào)窗口");
        sleep(1);
        NSLog(@"第2輛車 駛出 收費(fèi)站1號(hào)窗口");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"第3輛車 進(jìn)入 收費(fèi)站1號(hào)窗口");
        sleep(1);
        NSLog(@"第3輛車 駛出 收費(fèi)站1號(hào)窗口");
        dispatch_semaphore_signal(semaphore);
    });
}
///運(yùn)行結(jié)果
2018-11-09 11:56:47.264024+0800 iOS GCD[1598:54884] 第2輛車 進(jìn)入 收費(fèi)站1號(hào)窗口
2018-11-09 11:56:47.264026+0800 iOS GCD[1598:54882] 第1輛車 進(jìn)入 收費(fèi)站1號(hào)窗口
2018-11-09 11:56:48.264409+0800 iOS GCD[1598:54884] 第2輛車 駛出 收費(fèi)站1號(hào)窗口
2018-11-09 11:56:48.264409+0800 iOS GCD[1598:54882] 第1輛車 駛出 收費(fèi)站1號(hào)窗口
2018-11-09 11:56:48.264616+0800 iOS GCD[1598:54885] 第3輛車 進(jìn)入 收費(fèi)站1號(hào)窗口
2018-11-09 11:56:49.265146+0800 iOS GCD[1598:54885] 第3輛車 駛出 收費(fèi)站1號(hào)窗口

8 GCD 之 dispatch_group (隊(duì)列組)

GCD的隊(duì)列組可以將多個(gè)任務(wù)組合成一組,用于監(jiān)聽這一組任務(wù)是否全部完成。知道關(guān)聯(lián)的任務(wù)全部完成之后再發(fā)出通知以執(zhí)行其他操作。

  • 調(diào)用隊(duì)列組的 dispatch_group_async 先把任務(wù)放到隊(duì)列中,然后將隊(duì)列放入隊(duì)列組中?;蛘呤褂藐?duì)列組的 dispatch_group_enter、dispatch_group_leave 組合 來(lái)實(shí)現(xiàn)
    dispatch_group_async。
  • 調(diào)用隊(duì)列組的 dispatch_group_notify 回到指定線程執(zhí)行任務(wù)?;蛘呤褂?dispatch_group_wait 回到當(dāng)前線程繼續(xù)向下執(zhí)行(會(huì)阻塞當(dāng)前線程)。

8.1 dispatch_group_notify

監(jiān)聽任務(wù)的執(zhí)行情況,全部任務(wù)執(zhí)行完成之后,追加到Group中,執(zhí)行其他任務(wù)。

- (void)dispatchGroup {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_t group = dispatch_group_create();
    ///任務(wù)一
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1-----%d-----",i);
            sleep(2);
        }
    });
    ///任務(wù)二
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2-----%d-----",i);
            sleep(2);
        }
    });
    ///任務(wù)三
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3-----%d-----",i);
            sleep(2);
        }
    });
    
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"4-----%d-----",i);
            sleep(2);
        }
    });
}
///執(zhí)行結(jié)果
2018-11-09 14:18:07.450983+0800 iOS GCD[1782:97016] 3-----0-----
2018-11-09 14:18:07.450983+0800 iOS GCD[1782:97017] 2-----0-----
2018-11-09 14:18:07.450983+0800 iOS GCD[1782:97018] 1-----0-----
2018-11-09 14:18:09.456447+0800 iOS GCD[1782:97016] 3-----1-----
2018-11-09 14:18:09.456452+0800 iOS GCD[1782:97018] 1-----1-----
2018-11-09 14:18:09.456447+0800 iOS GCD[1782:97017] 2-----1-----
2018-11-09 14:18:11.459356+0800 iOS GCD[1782:97017] 4-----0-----
2018-11-09 14:18:13.464384+0800 iOS GCD[1782:97017] 4-----1-----

從執(zhí)行結(jié)果可以看出,在所有任務(wù)都執(zhí)行完成之后,才會(huì)執(zhí)行dispatch_group_notify中的代碼。

8.2 dispatch_group_wait

暫停(阻塞)當(dāng)前線程,等待執(zhí)行的Group都完成之后,才會(huì)繼續(xù)執(zhí)行。

- (void)dispatchGroup {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_t group = dispatch_group_create();
    ///任務(wù)一
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1-----%d-----",i);
            sleep(2);
        }
    });
    ///任務(wù)二
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2-----%d-----",i);
            sleep(2);
        }
    });
    ///任務(wù)三
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3-----%d-----",i);
            sleep(2);
        }
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"4------------");
}
///執(zhí)行結(jié)果
2018-11-09 14:23:25.062623+0800 iOS GCD[1802:99515] 2-----0-----
2018-11-09 14:23:25.062624+0800 iOS GCD[1802:99516] 3-----0-----
2018-11-09 14:23:25.062624+0800 iOS GCD[1802:99541] 1-----0-----
2018-11-09 14:23:27.065677+0800 iOS GCD[1802:99541] 1-----1-----
2018-11-09 14:23:27.065681+0800 iOS GCD[1802:99515] 2-----1-----
2018-11-09 14:23:27.065677+0800 iOS GCD[1802:99516] 3-----1-----
2018-11-09 14:23:29.066620+0800 iOS GCD[1802:99443] 4------------

從執(zhí)行結(jié)果可以看出,dispatch_group_wait會(huì)阻塞當(dāng)前線程。在group任務(wù)都執(zhí)行完成之后,才繼續(xù)往下執(zhí)行。

8.3 dispatch_group_enter、dispatch_group_leave

一般在網(wǎng)絡(luò)請(qǐng)求中,會(huì)使用此組合方法控制網(wǎng)絡(luò)請(qǐng)求順序。

  • dispatch_group_enter 標(biāo)志著一個(gè)任務(wù)追加到 group,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù)+1
  • dispatch_group_leave 標(biāo)志著一個(gè)任務(wù)離開了 group,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù)-1。
  • 當(dāng) group 中未執(zhí)行完畢任務(wù)數(shù)為0的時(shí)候,才會(huì)使dispatch_group_wait解除阻塞,以及執(zhí)行追加到dispatch_group_notify中的任務(wù)。
- (void)dispatchGroup {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_t group = dispatch_group_create();
    ///任務(wù)一
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1-----%d-----",i);
            sleep(2);
        }
        dispatch_group_leave(group);
    });
    ///任務(wù)二
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2-----%d-----",i);
            sleep(2);
        }
        dispatch_group_leave(group);
    });
    ///任務(wù)三
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3-----%d-----",i);
            sleep(2);
        }
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, queue, ^{
        NSLog(@"4-----------");
    });
}
///執(zhí)行結(jié)果
2018-11-09 14:34:03.145127+0800 iOS GCD[1860:105502] 3-----0-----
2018-11-09 14:34:03.145127+0800 iOS GCD[1860:105501] 1-----0-----
2018-11-09 14:34:03.145127+0800 iOS GCD[1860:105504] 2-----0-----
2018-11-09 14:34:05.148997+0800 iOS GCD[1860:105501] 1-----1-----
2018-11-09 14:34:05.148997+0800 iOS GCD[1860:105502] 3-----1-----
2018-11-09 14:34:05.148997+0800 iOS GCD[1860:105504] 2-----1-----
2018-11-09 14:34:07.151059+0800 iOS GCD[1860:105501] 4-----------

從執(zhí)行結(jié)果可以看出,在所有任務(wù)都執(zhí)行完成之后,才會(huì)執(zhí)行dispatch_group_notify中的代碼。

9 GCD 之 dispatch_after (延時(shí)任務(wù))

dispatch_after一般用于執(zhí)行延時(shí)任務(wù),但是需要注意的是:dispatch_after函數(shù)并不是在指定時(shí)間之后才開始執(zhí)行處理,而是在指定時(shí)間之后將任務(wù)追加到主隊(duì)列中。嚴(yán)格來(lái)說,這個(gè)時(shí)間并不是絕對(duì)準(zhǔn)確的,但想要大致延遲執(zhí)行任務(wù),dispatch_after函數(shù)是很有效的。

- (void)dispatchAfter{
    NSLog(@"----------begin----%@",[NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"----------延時(shí)任務(wù)--%@",[NSThread currentThread]);
    });
}
///執(zhí)行結(jié)果
2018-11-09 14:42:19.681291+0800 iOS GCD[1915:110037] ----------begin----<NSThread: 0x604000261e40>{number = 1, name = main}
2018-11-09 14:42:22.975073+0800 iOS GCD[1915:110037] ----------延時(shí)任務(wù)--<NSThread: 0x604000261e40>{number = 1, name = main}

10 GCD 之 dispatch_barrier_async 柵欄方法

在某些特殊情景下,我們需要執(zhí)行兩組操作,需要在第一組操作完成之后再執(zhí)行第二組操作,在此類需求下,我們可以雖然dispatch_groupdispatch_set_target_queue可以解決,但是實(shí)現(xiàn)過于復(fù)雜。這時(shí)我們可以使用dispatch_barrier_async方法。此函數(shù)更加的聰明。

他就像一個(gè)柵欄一樣,將這兩組任務(wù)分離開來(lái)。dispatch_barrier_async函數(shù)會(huì)等待前邊追加到并發(fā)隊(duì)列中的任務(wù)全部執(zhí)行完畢之后,再將指定的任務(wù)追加到該異步隊(duì)列中。然后在dispatch_barrier_async函數(shù)追加的任務(wù)執(zhí)行完畢之后,異步隊(duì)列才恢復(fù)為一般動(dòng)作,接著追加任務(wù)到該異步隊(duì)列并開始執(zhí)行。

注意
1.該函數(shù)需要同dispatch_queue_create函數(shù)生成的concurrent Dispatch Queue隊(duì)列一起使用(同dispatch_get_global_queue函數(shù)無(wú)效)
2.如果用的是串行隊(duì)列或者系統(tǒng)提供的全局并發(fā)隊(duì)列,這個(gè)柵欄函數(shù)的作用等同于一個(gè)同步函數(shù)的作用。

dispatch_barrier_async.png

代碼演示如下:

- (void)dispatch_barrier_sync {
    dispatch_queue_t queue = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"-----start-----");
    ///任務(wù)一
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            sleep(2);
            NSLog(@"1-----%d-----%@",i,[NSThread currentThread]);
        }
    });
    ///任務(wù)二
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            sleep(2);
            NSLog(@"2-----%d-----%@",i,[NSThread currentThread]);
        }
    });
    dispatch_barrier_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            sleep(2);
            NSLog(@"dispatch_barrier_sync-----%d-----%@",i,[NSThread currentThread]);
        }
    });
    NSLog(@"-----barrier-----");
    ///任務(wù)三
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            sleep(2);
            NSLog(@"3-----%d-----%@",i,[NSThread currentThread]);
        }
    });
    ///任務(wù)四
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            sleep(2);
            NSLog(@"4-----%d-----%@",i,[NSThread currentThread]);
        }
    });
    NSLog(@"-----end-----");
}
///執(zhí)行結(jié)果
2018-11-09 16:02:31.880398+0800 iOS GCD[2436:156316] -----start-----
2018-11-09 16:02:31.880669+0800 iOS GCD[2436:156316] -----barrier-----
2018-11-09 16:02:31.880965+0800 iOS GCD[2436:156316] -----end-----
2018-11-09 16:02:33.883236+0800 iOS GCD[2436:156454] 1-----0-----<NSThread: 0x600000273f40>{number = 4, name = (null)}
2018-11-09 16:02:33.883236+0800 iOS GCD[2436:156452] 2-----0-----<NSThread: 0x600000273e80>{number = 3, name = (null)}
2018-11-09 16:02:35.888045+0800 iOS GCD[2436:156452] 2-----1-----<NSThread: 0x600000273e80>{number = 3, name = (null)}
2018-11-09 16:02:35.888045+0800 iOS GCD[2436:156454] 1-----1-----<NSThread: 0x600000273f40>{number = 4, name = (null)}
2018-11-09 16:02:37.893215+0800 iOS GCD[2436:156452] dispatch_barrier_sync-----0-----<NSThread: 0x600000273e80>{number = 3, name = (null)}
2018-11-09 16:02:39.895096+0800 iOS GCD[2436:156452] dispatch_barrier_sync-----1-----<NSThread: 0x600000273e80>{number = 3, name = (null)}
2018-11-09 16:02:41.896703+0800 iOS GCD[2436:156454] 4-----0-----<NSThread: 0x600000273f40>{number = 4, name = (null)}
2018-11-09 16:02:41.896703+0800 iOS GCD[2436:156452] 3-----0-----<NSThread: 0x600000273e80>{number = 3, name = (null)}
2018-11-09 16:02:43.899571+0800 iOS GCD[2436:156452] 3-----1-----<NSThread: 0x600000273e80>{number = 3, name = (null)}
2018-11-09 16:02:43.899571+0800 iOS GCD[2436:156454] 4-----1-----<NSThread: 0x600000273f40>{number = 4, name = (null)}

從結(jié)果可以看到,dispatch_barrier_async將任務(wù)切分成了兩組,前面的任務(wù)執(zhí)行完成之后,執(zhí)行dispatch_barrier_async追加的任務(wù)。dispatch_barrier_async追加的任務(wù)完成之后,執(zhí)行后面的任務(wù)。

我們將dispatch_barrier_async切換為dispatch_barrier_sync時(shí),執(zhí)行結(jié)果如下:

2018-11-09 16:06:27.950577+0800 iOS GCD[2476:159238] -----start-----<NSThread: 0x6000000693c0>{number = 1, name = main}
2018-11-09 16:06:29.952112+0800 iOS GCD[2476:159330] 1-----0-----<NSThread: 0x60400027cec0>{number = 4, name = (null)}
2018-11-09 16:06:29.952115+0800 iOS GCD[2476:159332] 2-----0-----<NSThread: 0x600000262380>{number = 3, name = (null)}
2018-11-09 16:06:31.953935+0800 iOS GCD[2476:159330] 1-----1-----<NSThread: 0x60400027cec0>{number = 4, name = (null)}
2018-11-09 16:06:31.953935+0800 iOS GCD[2476:159332] 2-----1-----<NSThread: 0x600000262380>{number = 3, name = (null)}
2018-11-09 16:06:33.955469+0800 iOS GCD[2476:159238] dispatch_barrier_sync-----0-----<NSThread: 0x6000000693c0>{number = 1, name = main}
2018-11-09 16:06:35.955883+0800 iOS GCD[2476:159238] dispatch_barrier_sync-----1-----<NSThread: 0x6000000693c0>{number = 1, name = main}
2018-11-09 16:06:35.956068+0800 iOS GCD[2476:159238] -----barrier-----<NSThread: 0x6000000693c0>{number = 1, name = main}
2018-11-09 16:06:35.956225+0800 iOS GCD[2476:159238] -----end-----<NSThread: 0x6000000693c0>{number = 1, name = main}
2018-11-09 16:06:37.959516+0800 iOS GCD[2476:159332] 4-----0-----<NSThread: 0x600000262380>{number = 3, name = (null)}
2018-11-09 16:06:37.959516+0800 iOS GCD[2476:159331] 3-----0-----<NSThread: 0x60400027aa40>{number = 5, name = (null)}
2018-11-09 16:06:39.961185+0800 iOS GCD[2476:159331] 3-----1-----<NSThread: 0x60400027aa40>{number = 5, name = (null)}
2018-11-09 16:06:39.961185+0800 iOS GCD[2476:159332] 4-----1-----<NSThread: 0x600000262380>{number = 3, name = (null)}

通過分析可以看出:

  • dispatch_barrier_sync需要等待執(zhí)行完?yáng)艡谇懊娴娜蝿?wù)之后,才會(huì)追加?xùn)艡谥蟮娜蝿?wù)。
  • dispatch_barrier_async無(wú)需等待柵欄執(zhí)行完,會(huì)將任務(wù)都追加到隊(duì)列里面,只是暫時(shí)不執(zhí)行而已。

11 GCD 之 dispatch_set_target_queue

dispatch_set_target_queue主要有兩個(gè)主要功能

  1. 使用dispatch_set_target_queue更改Dispatch Queue的執(zhí)行優(yōu)先級(jí)
    dispatch_queue_create函數(shù)生成的DisPatch Queue不管是Serial DisPatch Queue還是Concurrent Dispatch Queue,執(zhí)行的優(yōu)先級(jí)都與默認(rèn)優(yōu)先級(jí)的Global Dispatch queue相同,如果需要變更生成的Dispatch Queue的執(zhí)行優(yōu)先級(jí)則需要使用dispatch_set_target_queue函數(shù)
- (void)testTeagerQueue1 {
     dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL);
     dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
     dispatch_set_target_queue(serialQueue, globalQueue);
     // 第一個(gè)參數(shù)為要設(shè)置優(yōu)先級(jí)的queue,第二個(gè)參數(shù)是參照物,既將第一個(gè)queue的優(yōu)先級(jí)和第二個(gè)queue的優(yōu)先級(jí)設(shè)置一樣。
 }

注意:不要將系統(tǒng)系統(tǒng)的Main Dispatch Queue和Global Dispatch Queue修改優(yōu)先級(jí),因?yàn)橛锌赡艹霈F(xiàn)不可控狀況。

  1. 使用dispatch_set_target_queue修改用戶隊(duì)列的目標(biāo)隊(duì)列,使多個(gè)serial queue在目標(biāo)queue上一次只有一個(gè)執(zhí)行

注意:我們需要闡述一下生成多個(gè)Serial DisPatch Queue時(shí)的注意事項(xiàng)
Serial DisPatch Queue是一個(gè)串行隊(duì)列,只能同時(shí)執(zhí)行1個(gè)追加處理(即任務(wù)),當(dāng)用Dispatch_queue_create函數(shù)生成多個(gè)Serial DisPatch Queue時(shí),每個(gè)Serial DisPatch Queue均獲得一個(gè)線程,即多個(gè)Serial DisPatch Queue可并發(fā)執(zhí)行,同時(shí)處理添加到各個(gè)Serial DisPatch Queue中的任務(wù),但要注意如果過多地使用多線程,就會(huì)消耗大量?jī)?nèi)存,引起大量的上下文切換,大幅度降低系統(tǒng)的響應(yīng)性能,所以我們只在為了避免多個(gè)線程更新相同資源導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)時(shí),使用Serial DisPatch Queue

第一種情況:使用dispatch_set_target_queue(Dispatch Queue1, Dispatch Queue2)實(shí)現(xiàn)隊(duì)列的動(dòng)態(tài)調(diào)度管理

  - (void)testTargetQueue2 {
      //創(chuàng)建一個(gè)串行隊(duì)列queue1
      dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
      //創(chuàng)建一個(gè)串行隊(duì)列queue2
      dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
      
      //使用dispatch_set_target_queue()實(shí)現(xiàn)隊(duì)列的動(dòng)態(tài)調(diào)度管理
      dispatch_set_target_queue(queue1, queue2);
      
 /*
     <*>dispatch_set_target_queue(Dispatch Queue1, Dispatch Queue2);
     那么dispatchA上還未運(yùn)行的block會(huì)在dispatchB上運(yùn)行。這時(shí)如果暫停dispatchA運(yùn)行:
     
     <*>dispatch_suspend(dispatchA);
     這時(shí)則只會(huì)暫停dispatchA上原來(lái)的block的執(zhí)行,dispatchB的block則不受影響。而如果暫停dispatchB的運(yùn)行,則會(huì)暫停dispatchA的運(yùn)行。
     
     這里只簡(jiǎn)單舉個(gè)例子,說明dispatch隊(duì)列運(yùn)行的靈活性,在實(shí)際應(yīng)用中你會(huì)逐步發(fā)掘出它的潛力。
     
     dispatch隊(duì)列不支持cancel(取消),沒有實(shí)現(xiàn)dispatch_cancel()函數(shù),不像NSOperationQueue,不得不說這是個(gè)小小的缺憾
      
 */
     dispatch_async(queue1, ^{
         for (NSInteger i = 0; i < 10; i++) {
             NSLog(@"queue1:%@, %ld", [NSThread currentThread], i);
             [NSThread sleepForTimeInterval:0.5];
             if (i == 5) {
                 dispatch_suspend(queue2);
             }
         }
     });
     
     dispatch_async(queue1, ^{
         for (NSInteger i = 0; i < 100; i++) {
             NSLog(@"queue1:%@, %ld", [NSThread currentThread], i);
         }
         
     });
     
     dispatch_async(queue2, ^{
         for (NSInteger i = 0; i < 100; i++) {
             NSLog(@"queue2:%@, %ld", [NSThread currentThread], i);
         }
     });
     
 }

第二種情況:使用dispatch_set_target_queue將多個(gè)串行的queue指定到了同一目標(biāo),那么著多個(gè)串行queue在目標(biāo)queue上就是同步執(zhí)行的,不再是并行執(zhí)行。

- (void)testTargetQueue {
    //1.創(chuàng)建目標(biāo)隊(duì)列
    dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
    
    //2.創(chuàng)建3個(gè)串行隊(duì)列
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
    
    //3.將3個(gè)串行隊(duì)列分別添加到目標(biāo)隊(duì)列
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_set_target_queue(queue3, targetQueue);
    
    
    dispatch_async(queue1, ^{
        NSLog(@"1 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"1 out");
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"2 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"2 out");
    });
    dispatch_async(queue3, ^{
        NSLog(@"3 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"3 out");
    });
}

12 dispatch_suspend / dispatch_resume

在我們追加大量任務(wù)到dispatch_Queue中時(shí),有時(shí)希望不執(zhí)行已經(jīng)追加的任務(wù)。在這種情況下可以使用掛起函數(shù)dispatch_suspend,當(dāng)可以執(zhí)行時(shí)在恢復(fù)即可dispatch_resume。

  • dispatch_suspend(queue):掛起指定的Queue
  • dispatch_resume(queue):恢復(fù)指定的Queue

這兩個(gè)函數(shù)對(duì)已經(jīng)執(zhí)行的任務(wù)沒有影響。掛起后,追加到dispatch_Queue中但尚未處理的任務(wù)在此之后停止執(zhí)行。而恢復(fù)則使得這些任務(wù)繼續(xù)執(zhí)行。

本文參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文用來(lái)介紹 iOS 多線程中 GCD 的相關(guān)知識(shí)以及使用方法。這大概是史上最詳細(xì)、清晰的關(guān)于 GCD 的詳細(xì)講...
    花花世界的孤獨(dú)行者閱讀 578評(píng)論 0 1
  • iOS多線程編程 基本知識(shí) 1. 進(jìn)程(process) 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,就是一段程序的執(zhí)...
    陵無(wú)山閱讀 6,351評(píng)論 1 14
  • 本文首發(fā)于我的個(gè)人博客:「程序員充電站」[https://itcharge.cn]文章鏈接:「?jìng)魉烷T」[https...
    ITCharge閱讀 350,709評(píng)論 308 1,927
  • -1- 看著對(duì)方的眼睛說話 遇到無(wú)論如何都不想放棄溝通的時(shí)候,就算雙方意見嚴(yán)重分歧,針鋒相對(duì),也一定要直視著對(duì)方的...
    小落墨smile閱讀 204評(píng)論 4 3
  • 朋友是做快消的業(yè)務(wù)員,在崗位上勤勤懇懇的干著,但是銷售業(yè)績(jī)就是不怎么樣,干了好幾年業(yè)務(wù)員還是業(yè)務(wù)員,也不是干活偷懶...
    lh事在人為閱讀 265評(píng)論 0 0

友情鏈接更多精彩內(nèi)容