iOS多線程:『GCD』詳盡總結(jié)

行走的少年郎 關(guān)注

2016.09.03 19:47* 字?jǐn)?shù) 10492 閱讀 41799評(píng)論 91喜歡 504

image.png
  • 本文更新時(shí)間:2018-02-24 10:07:40

感謝大家對(duì)這篇文章的喜歡和支持。為了不辜負(fù)大家的喜歡,也為了更好的讓大家了解 iOS 多線程斥难,以及 GCD 的相關(guān)知識(shí),我對(duì)這篇文章進(jìn)行了重新梳理乍构,在原有文章的基礎(chǔ)上修改了原文存在的問題,并增加了更多關(guān)于 GCD 相關(guān)知識(shí)和使用方法扛点,希望大家能夠喜歡這篇新文章哥遮。

本文用來介紹 iOS 多線程中 GCD 的相關(guān)知識(shí)以及使用方法。這大概是史上最詳細(xì)陵究、清晰的關(guān)于 GCD 的詳細(xì)講解+總結(jié)的文章了眠饮。通過本文,您將了解到:

1. GCD 簡(jiǎn)介

2. GCD 任務(wù)和隊(duì)列

3. GCD 的使用步驟

4. GCD 的基本使用(6種不同組合區(qū)別)

5. GCD 線程間的通信

6. GCD 的其他方法(柵欄方法:dispatch_barrier_async铜邮、延時(shí)執(zhí)行方法:dispatch_after仪召、一次性代碼(只執(zhí)行一次):dispatch_once、快速迭代方法:dispatch_apply松蒜、隊(duì)列組:dispatch_group扔茅、信號(hào)量:dispatch_semaphore)

文中 Demo 我已放在了 Github 上,Demo 鏈接:傳送門

1. GCD 簡(jiǎn)介

什么是 GCD 呢秸苗?我們先來看看百度百科的解釋簡(jiǎn)單了解下概念

引自百度百科

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

為什么要用 GCD 呢胁后?

因?yàn)?GCD 有很多好處啊,具體如下:

  • GCD 可用于多核的并行運(yùn)算

  • GCD 會(huì)自動(dòng)利用更多的 CPU 內(nèi)核(比如雙核嗦枢、四核)

  • GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程攀芯、調(diào)度任務(wù)、銷毀線程)

  • 程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù)文虏,不需要編寫任何線程管理代碼

既然 GCD 有這么多的好處侣诺,那么下面我們就來系統(tǒng)的學(xué)習(xí)一下 GCD 的使用方法。

2. GCD 任務(wù)和隊(duì)列

學(xué)習(xí) GCD 之前氧秘,先來了解 GCD 中兩個(gè)核心概念:任務(wù)和隊(duì)列年鸳。

任務(wù):就是執(zhí)行操作的意思,換句話說就是你在線程中執(zhí)行的那段代碼丸相。在 GCD 中是放在 block 中的搔确。執(zhí)行任務(wù)有兩種方式:同步執(zhí)行(sync)和異步執(zhí)行(async)。兩者的主要區(qū)別是:是否等待隊(duì)列的任務(wù)執(zhí)行結(jié)束,以及是否具備開啟新線程的能力膳算。

  • 同步執(zhí)行(sync):

    • 同步添加任務(wù)到指定的隊(duì)列中座硕,在添加的任務(wù)執(zhí)行結(jié)束之前,會(huì)一直等待涕蜂,直到隊(duì)列里面的任務(wù)完成之后再繼續(xù)執(zhí)行华匾。

    • 只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力机隙。

  • 異步執(zhí)行(async):

    • 異步添加任務(wù)到指定的隊(duì)列中蜘拉,它不會(huì)做任何等待,可以繼續(xù)執(zhí)行任務(wù)黍瞧。

    • 可以在新的線程中執(zhí)行任務(wù)诸尽,具備開啟新線程的能力。

舉個(gè)簡(jiǎn)單例子:你要打電話給小明和小白印颤。

同步執(zhí)行就是您机,你打電話給小明的時(shí)候,不能同時(shí)打給小白年局,等到給小明打完了际看,才能打給小白(等待任務(wù)執(zhí)行結(jié)束)。而且只能用當(dāng)前的電話(不具備開啟新線程的能力)矢否。

而異步執(zhí)行就是仲闽,你打電話給小明的時(shí)候,不等和小明通話結(jié)束僵朗,還能直接給小白打電話赖欣,不用等著和小明通話結(jié)束再打(不用等待任務(wù)執(zhí)行結(jié)束)。除了當(dāng)前電話验庙,你還可以使用其他所能使用的電話(具備開啟新線程的能力)顶吮。

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

隊(duì)列(Dispatch Queue):這里的隊(duì)列指執(zhí)行任務(wù)的等待隊(duì)列,即用來存放任務(wù)的隊(duì)列违寿。隊(duì)列是一種特殊的線性表湃交,采用 FIFO(先進(jìn)先出)的原則,即新任務(wù)總是被插入到隊(duì)列的末尾藤巢,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開始讀取搞莺。每讀取一個(gè)任務(wù),則從隊(duì)列中釋放一個(gè)任務(wù)掂咒。隊(duì)列的結(jié)構(gòu)可參考下圖:

image.png

隊(duì)列(Dispatch Queue).png

在 GCD 中有兩種隊(duì)列:串行隊(duì)列和并發(fā)隊(duì)列腮敌。兩者都符合 FIFO(先進(jìn)先出)的原則阱当。兩者的主要區(qū)別是:執(zhí)行順序不同,以及開啟線程數(shù)不同糜工。

  • 串行隊(duì)列(Serial Dispatch Queue):

    • 每次只有一個(gè)任務(wù)被執(zhí)行弊添。讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行。(只開啟一個(gè)線程捌木,一個(gè)任務(wù)執(zhí)行完畢后油坝,再執(zhí)行下一個(gè)任務(wù))
  • 并發(fā)隊(duì)列(Concurrent Dispatch Queue):

    • 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行。(可以開啟多個(gè)線程刨裆,并且同時(shí)執(zhí)行任務(wù))

注意:并發(fā)隊(duì)列的并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效

兩者具體區(qū)別如下兩圖所示澈圈。

image.png

串行隊(duì)列.png

image.png

并發(fā)隊(duì)列.png

3. GCD 的使用步驟

GCD 的使用步驟其實(shí)很簡(jiǎn)單,只有兩步帆啃。

  1. 創(chuàng)建一個(gè)隊(duì)列(串行隊(duì)列或并發(fā)隊(duì)列)

  2. 將任務(wù)追加到任務(wù)的等待隊(duì)列中瞬女,然后系統(tǒng)就會(huì)根據(jù)任務(wù)類型執(zhí)行任務(wù)(同步執(zhí)行或異步執(zhí)行)

下邊來看看隊(duì)列的創(chuàng)建方法/獲取方法,以及任務(wù)的創(chuàng)建方法努潘。

3.1 隊(duì)列的創(chuàng)建方法/獲取方法

  • 可以使用dispatch_queue_create來創(chuàng)建隊(duì)列诽偷,需要傳入兩個(gè)參數(shù),第一個(gè)參數(shù)表示隊(duì)列的唯一標(biāo)識(shí)符疯坤,用于 DEBUG报慕,可為空,Dispatch Queue 的名稱推薦使用應(yīng)用程序 ID 這種逆序全程域名压怠;第二個(gè)參數(shù)用來識(shí)別是串行隊(duì)列還是并發(fā)隊(duì)列眠冈。DISPATCH_QUEUE_SERIAL 表示串行隊(duì)列,DISPATCH_QUEUE_CONCURRENT 表示并發(fā)隊(duì)列菌瘫。

// 串行隊(duì)列的創(chuàng)建方法

dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);

// 并發(fā)隊(duì)列的創(chuàng)建方法

dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

  • 對(duì)于串行隊(duì)列蜗顽,GCD 提供了的一種特殊的串行隊(duì)列:主隊(duì)列(Main Dispatch Queue)。

    • 所有放在主隊(duì)列中的任務(wù)雨让,都會(huì)放到主線程中執(zhí)行诫舅。

    • 可使用dispatch_get_main_queue()獲得主隊(duì)列。

// 主隊(duì)列的獲取方法

dispatch_queue_t queue = dispatch_get_main_queue();

  • 對(duì)于并發(fā)隊(duì)列宫患,GCD 默認(rèn)提供了全局并發(fā)隊(duì)列(Global Dispatch Queue)。

    • 可以使用dispatch_get_global_queue來獲取这弧。需要傳入兩個(gè)參數(shù)娃闲。第一個(gè)參數(shù)表示隊(duì)列優(yōu)先級(jí),一般用DISPATCH_QUEUE_PRIORITY_DEFAULT匾浪。第二個(gè)參數(shù)暫時(shí)沒用皇帮,用0即可。

// 全局并發(fā)隊(duì)列的獲取方法

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3.2 任務(wù)的創(chuàng)建方法

GCD 提供了同步執(zhí)行任務(wù)的創(chuàng)建方法dispatch_sync和異步執(zhí)行任務(wù)創(chuàng)建方法dispatch_async蛋辈。

// 同步執(zhí)行任務(wù)創(chuàng)建方法

dispatch_sync(queue, ^{

// 這里放同步執(zhí)行任務(wù)代碼

});

// 異步執(zhí)行任務(wù)創(chuàng)建方法

dispatch_async(queue, ^{

// 這里放異步執(zhí)行任務(wù)代碼

});

雖然使用 GCD 只需兩步属拾,但是既然我們有兩種隊(duì)列(串行隊(duì)列/并發(fā)隊(duì)列)将谊,兩種任務(wù)執(zhí)行方式(同步執(zhí)行/異步執(zhí)行),那么我們就有了四種不同的組合方式渐白。這四種不同的組合方式是:

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

實(shí)際上尊浓,剛才還說了兩種特殊隊(duì)列:全局并發(fā)隊(duì)列、主隊(duì)列纯衍。全局并發(fā)隊(duì)列可以作為普通并發(fā)隊(duì)列來使用。但是主隊(duì)列因?yàn)橛悬c(diǎn)特殊,所以我們就又多了兩種組合方式俺祠。這樣就有六種不同的組合方式了困鸥。

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

那么這幾種不同組合方式各有什么區(qū)別呢,這里為了方便歌亲,先上結(jié)果菇用,再來講解。你可以直接查看表格結(jié)果陷揪,然后跳過 4. GCD的基本使用 惋鸥。

<colgroup><col style="width: 130px;"><col style="width: 130px;"><col style="width: 130px;"><col style="width: 130px;"></colgroup>
|

區(qū)別

|

并發(fā)隊(duì)列

|

串行隊(duì)列

|

主隊(duì)列

|
|

同步(sync)

|

沒有開啟新線程,串行執(zhí)行任務(wù)

|

沒有開啟新線程鹅龄,串行執(zhí)行任務(wù)

|

沒有開啟新線程揩慕,串行執(zhí)行任務(wù)

|
|

異步(async)

|

有開啟新線程,并發(fā)執(zhí)行任務(wù)

|

有開啟新線程(1條)扮休,串行執(zhí)行任務(wù)

|

沒有開啟新線程迎卤,串行執(zhí)行任務(wù)

|

下邊我們來分別講講這幾種不同的組合方式的使用方法。

4. GCD 的基本使用

先來講講并發(fā)隊(duì)列的兩種執(zhí)行方式玷坠。

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

  • 在當(dāng)前線程中執(zhí)行任務(wù)蜗搔,不會(huì)開啟新線程,執(zhí)行完一個(gè)任務(wù)八堡,再執(zhí)行下一個(gè)任務(wù)樟凄。

/**

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

  • 特點(diǎn):在當(dāng)前線程中執(zhí)行任務(wù),不會(huì)開啟新線程兄渺,執(zhí)行完一個(gè)任務(wù)缝龄,再執(zhí)行下一個(gè)任務(wù)。

*/

  • (void)syncConcurrent {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"syncConcurrent---begin");

    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];              // 模擬耗時(shí)操作
    
          NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_sync(queue, ^{

      // 追加任務(wù)2
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_sync(queue, ^{

      // 追加任務(wù)3
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    NSLog(@"syncConcurrent---end");

}

輸出結(jié)果:

2018-02-23 20:34:55.095932+0800 YSC-GCD-demo[19892:4996930] currentThread---<NSThread: 0x60400006bbc0>{number = 1, name = main}

2018-02-23 20:34:55.096086+0800 YSC-GCD-demo[19892:4996930] syncConcurrent---begin

2018-02-23 20:34:57.097589+0800 YSC-GCD-demo[19892:4996930] 1---<NSThread: 0x60400006bbc0>{number = 1, name = main}

2018-02-23 20:34:59.099100+0800 YSC-GCD-demo[19892:4996930] 1---<NSThread: 0x60400006bbc0>{number = 1, name = main}

2018-02-23 20:35:01.099843+0800 YSC-GCD-demo[19892:4996930] 2---<NSThread: 0x60400006bbc0>{number = 1, name = main}

2018-02-23 20:35:03.101171+0800 YSC-GCD-demo[19892:4996930] 2---<NSThread: 0x60400006bbc0>{number = 1, name = main}

2018-02-23 20:35:05.101750+0800 YSC-GCD-demo[19892:4996930] 3---<NSThread: 0x60400006bbc0>{number = 1, name = main}

2018-02-23 20:35:07.102414+0800 YSC-GCD-demo[19892:4996930] 3---<NSThread: 0x60400006bbc0>{number = 1, name = main}

2018-02-23 20:35:07.102575+0800 YSC-GCD-demo[19892:4996930] syncConcurrent---end

從同步執(zhí)行 + 并發(fā)隊(duì)列中可看到:

  • 所有任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行的挂谍,沒有開啟新的線程(同步執(zhí)行不具備開啟新線程的能力)叔壤。

  • 所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行的(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)。

  • 任務(wù)按順序執(zhí)行的口叙。按順序執(zhí)行的原因:雖然并發(fā)隊(duì)列可以開啟多個(gè)線程炼绘,并且同時(shí)執(zhí)行多個(gè)任務(wù)。但是因?yàn)楸旧聿荒軇?chuàng)建新線程妄田,只有當(dāng)前線程這一個(gè)線程(同步任務(wù)不具備開啟新線程的能力)俺亮,所以也就不存在并發(fā)驮捍。而且當(dāng)前線程只有等待當(dāng)前隊(duì)列中正在執(zhí)行的任務(wù)執(zhí)行完畢之后,才能繼續(xù)接著執(zhí)行下面的操作(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)脚曾。所以任務(wù)只能一個(gè)接一個(gè)按順序執(zhí)行东且,不能同時(shí)被執(zhí)行。

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

  • 可以開啟多個(gè)線程斟珊,任務(wù)交替(同時(shí))執(zhí)行苇倡。

/**

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

  • 特點(diǎn):可以開啟多個(gè)線程,任務(wù)交替(同時(shí))執(zhí)行囤踩。

*/

  • (void)asyncConcurrent {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"asyncConcurrent---begin");

    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];              // 模擬耗時(shí)操作
    
          NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_async(queue, ^{

      // 追加任務(wù)2
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_async(queue, ^{

      // 追加任務(wù)3
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    NSLog(@"asyncConcurrent---end");

}

輸出結(jié)果:

2018-02-23 20:36:41.769269+0800 YSC-GCD-demo[19929:5005237] currentThread---<NSThread: 0x604000062d80>{number = 1, name = main}

2018-02-23 20:36:41.769496+0800 YSC-GCD-demo[19929:5005237] asyncConcurrent---begin

2018-02-23 20:36:41.769725+0800 YSC-GCD-demo[19929:5005237] asyncConcurrent---end

2018-02-23 20:36:43.774442+0800 YSC-GCD-demo[19929:5005566] 2---<NSThread: 0x604000266f00>{number = 5, name = (null)}

2018-02-23 20:36:43.774440+0800 YSC-GCD-demo[19929:5005567] 3---<NSThread: 0x60000026f200>{number = 4, name = (null)}

2018-02-23 20:36:43.774440+0800 YSC-GCD-demo[19929:5005565] 1---<NSThread: 0x600000264800>{number = 3, name = (null)}

2018-02-23 20:36:45.779286+0800 YSC-GCD-demo[19929:5005567] 3---<NSThread: 0x60000026f200>{number = 4, name = (null)}

2018-02-23 20:36:45.779302+0800 YSC-GCD-demo[19929:5005565] 1---<NSThread: 0x600000264800>{number = 3, name = (null)}

2018-02-23 20:36:45.779286+0800 YSC-GCD-demo[19929:5005566] 2---<NSThread: 0x604000266f00>{number = 5, name = (null)}

在異步執(zhí)行 + 并發(fā)隊(duì)列中可以看出:

  • 除了當(dāng)前線程(主線程)旨椒,系統(tǒng)又開啟了3個(gè)線程,并且任務(wù)是交替/同時(shí)執(zhí)行的堵漱。(異步執(zhí)行具備開啟新線程的能力综慎。且并發(fā)隊(duì)列可開啟多個(gè)線程,同時(shí)執(zhí)行多個(gè)任務(wù))勤庐。

  • 所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才執(zhí)行的示惊。說明當(dāng)前線程沒有等待,而是直接開啟了新線程愉镰,在新線程中執(zhí)行任務(wù)(異步執(zhí)行不做等待米罚,可以繼續(xù)執(zhí)行任務(wù))。

接下來再來講講串行隊(duì)列的兩種執(zhí)行方式丈探。

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

  • 不會(huì)開啟新線程录择,在當(dāng)前線程執(zhí)行任務(wù)。任務(wù)是串行的碗降,執(zhí)行完一個(gè)任務(wù)隘竭,再執(zhí)行下一個(gè)任務(wù)。

/**

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

  • 特點(diǎn):不會(huì)開啟新線程讼渊,在當(dāng)前線程執(zhí)行任務(wù)动看。任務(wù)是串行的,執(zhí)行完一個(gè)任務(wù)爪幻,再執(zhí)行下一個(gè)任務(wù)菱皆。

*/

  • (void)syncSerial {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"syncSerial---begin");

    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];              // 模擬耗時(shí)操作
    
          NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_sync(queue, ^{

      // 追加任務(wù)2
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_sync(queue, ^{

      // 追加任務(wù)3
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    NSLog(@"syncSerial---end");

}

輸出結(jié)果為:

2018-02-23 20:39:37.876811+0800 YSC-GCD-demo[19975:5017162] currentThread---<NSThread: 0x604000079400>{number = 1, name = main}

2018-02-23 20:39:37.876998+0800 YSC-GCD-demo[19975:5017162] syncSerial---begin

2018-02-23 20:39:39.878316+0800 YSC-GCD-demo[19975:5017162] 1---<NSThread: 0x604000079400>{number = 1, name = main}

2018-02-23 20:39:41.879829+0800 YSC-GCD-demo[19975:5017162] 1---<NSThread: 0x604000079400>{number = 1, name = main}

2018-02-23 20:39:43.880660+0800 YSC-GCD-demo[19975:5017162] 2---<NSThread: 0x604000079400>{number = 1, name = main}

2018-02-23 20:39:45.881265+0800 YSC-GCD-demo[19975:5017162] 2---<NSThread: 0x604000079400>{number = 1, name = main}

2018-02-23 20:39:47.882257+0800 YSC-GCD-demo[19975:5017162] 3---<NSThread: 0x604000079400>{number = 1, name = main}

2018-02-23 20:39:49.883008+0800 YSC-GCD-demo[19975:5017162] 3---<NSThread: 0x604000079400>{number = 1, name = main}

2018-02-23 20:39:49.883253+0800 YSC-GCD-demo[19975:5017162] syncSerial---end

在同步執(zhí)行 + 串行隊(duì)列可以看到:

  • 所有任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行的,并沒有開啟新的線程(同步執(zhí)行不具備開啟新線程的能力)挨稿。

  • 所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)仇轻。

  • 任務(wù)是按順序執(zhí)行的(串行隊(duì)列每次只有一個(gè)任務(wù)被執(zhí)行,任務(wù)一個(gè)接一個(gè)按順序執(zhí)行)叶组。

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

  • 會(huì)開啟新線程,但是因?yàn)槿蝿?wù)是串行的历造,執(zhí)行完一個(gè)任務(wù)甩十,再執(zhí)行下一個(gè)任務(wù)

/**

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

  • 特點(diǎn):會(huì)開啟新線程船庇,但是因?yàn)槿蝿?wù)是串行的,執(zhí)行完一個(gè)任務(wù)侣监,再執(zhí)行下一個(gè)任務(wù)鸭轮。

*/

  • (void)asyncSerial {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"asyncSerial---begin");

    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];              // 模擬耗時(shí)操作
    
          NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_async(queue, ^{

      // 追加任務(wù)2
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_async(queue, ^{

      // 追加任務(wù)3
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    NSLog(@"asyncSerial---end");

}

輸出結(jié)果為:

2018-02-23 20:41:17.029999+0800 YSC-GCD-demo[20008:5024757] currentThread---<NSThread: 0x604000070440>{number = 1, name = main}

2018-02-23 20:41:17.030212+0800 YSC-GCD-demo[20008:5024757] asyncSerial---begin

2018-02-23 20:41:17.030364+0800 YSC-GCD-demo[20008:5024757] asyncSerial---end

2018-02-23 20:41:19.035379+0800 YSC-GCD-demo[20008:5024950] 1---<NSThread: 0x60000026e100>{number = 3, name = (null)}

2018-02-23 20:41:21.037140+0800 YSC-GCD-demo[20008:5024950] 1---<NSThread: 0x60000026e100>{number = 3, name = (null)}

2018-02-23 20:41:23.042220+0800 YSC-GCD-demo[20008:5024950] 2---<NSThread: 0x60000026e100>{number = 3, name = (null)}

2018-02-23 20:41:25.042971+0800 YSC-GCD-demo[20008:5024950] 2---<NSThread: 0x60000026e100>{number = 3, name = (null)}

2018-02-23 20:41:27.047690+0800 YSC-GCD-demo[20008:5024950] 3---<NSThread: 0x60000026e100>{number = 3, name = (null)}

2018-02-23 20:41:29.052327+0800 YSC-GCD-demo[20008:5024950] 3---<NSThread: 0x60000026e100>{number = 3, name = (null)}

在異步執(zhí)行 + 串行隊(duì)列可以看到:

  • 開啟了一條新線程(異步執(zhí)行具備開啟新線程的能力,串行隊(duì)列只開啟一個(gè)線程)橄霉。

  • 所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的(異步執(zhí)行不會(huì)做任何等待窃爷,可以繼續(xù)執(zhí)行任務(wù))。

  • 任務(wù)是按順序執(zhí)行的(串行隊(duì)列每次只有一個(gè)任務(wù)被執(zhí)行姓蜂,任務(wù)一個(gè)接一個(gè)按順序執(zhí)行)按厘。

下邊講講剛才我們提到過的特殊隊(duì)列:主隊(duì)列。

  • 主隊(duì)列:GCD自帶的一種特殊的串行隊(duì)列

    • 所有放在主隊(duì)列中的任務(wù)钱慢,都會(huì)放到主線程中執(zhí)行

    • 可使用dispatch_get_main_queue()獲得主隊(duì)列

我們?cè)賮砜纯粗麝?duì)列的兩種組合方式逮京。

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

同步執(zhí)行 + 主隊(duì)列在不同線程中調(diào)用結(jié)果也是不一樣,在主線程中調(diào)用會(huì)出現(xiàn)死鎖束莫,而在其他線程中則不會(huì)懒棉。

4.5.1 在主線程中調(diào)用同步執(zhí)行 + 主隊(duì)列

  • 互相等待卡住不可行

/**

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

  • 特點(diǎn)(主線程調(diào)用):互等卡主不執(zhí)行。

  • 特點(diǎn)(其他線程調(diào)用):不會(huì)開啟新線程览绿,執(zhí)行完一個(gè)任務(wù)策严,再執(zhí)行下一個(gè)任務(wù)。

*/

  • (void)syncMain {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"syncMain---begin");

    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_sync(queue, ^{

      // 追加任務(wù)1
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_sync(queue, ^{

      // 追加任務(wù)2
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_sync(queue, ^{

      // 追加任務(wù)3
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    NSLog(@"syncMain---end");

}

輸出結(jié)果

2018-02-23 20:42:36.842892+0800 YSC-GCD-demo[20041:5030982] currentThread---<NSThread: 0x600000078a00>{number = 1, name = main}

2018-02-23 20:42:36.843050+0800 YSC-GCD-demo[20041:5030982] syncMain---begin

(lldb)

在同步執(zhí)行 + 主隊(duì)列可以驚奇的發(fā)現(xiàn):

  • 在主線程中使用同步執(zhí)行 + 主隊(duì)列饿敲,追加到主線程的任務(wù)1妻导、任務(wù)2、任務(wù)3都不再執(zhí)行了诀蓉,而且syncMain---end也沒有打印栗竖,在XCode 9上還會(huì)報(bào)崩潰。這是為什么呢渠啤?

這是因?yàn)槲覀冊(cè)谥骶€程中執(zhí)行syncMain方法狐肢,相當(dāng)于把syncMain任務(wù)放到了主線程的隊(duì)列中。而同步執(zhí)行會(huì)等待當(dāng)前隊(duì)列中的任務(wù)執(zhí)行完畢沥曹,才會(huì)接著執(zhí)行份名。那么當(dāng)我們把任務(wù)1追加到主隊(duì)列中,任務(wù)1就在等待主線程處理完syncMain任務(wù)妓美。而syncMain任務(wù)需要等待任務(wù)1執(zhí)行完畢僵腺,才能接著執(zhí)行。

那么壶栋,現(xiàn)在的情況就是syncMain任務(wù)和任務(wù)1都在等對(duì)方執(zhí)行完畢辰如。這樣大家互相等待,所以就卡住了贵试,所以我們的任務(wù)執(zhí)行不了琉兜,而且syncMain---end也沒有打印凯正。

要是如果不在主線程中調(diào)用,而在其他線程中調(diào)用會(huì)如何呢豌蟋?

4.5.2 在其他線程中調(diào)用同步執(zhí)行 + 主隊(duì)列

  • 不會(huì)開啟新線程廊散,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)

// 使用 NSThread 的 detachNewThreadSelector 方法會(huì)創(chuàng)建線程梧疲,并自動(dòng)啟動(dòng)線程執(zhí)行

selector 任務(wù)

[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];

輸出結(jié)果:

2018-02-23 20:44:19.377321+0800 YSC-GCD-demo[20083:5040347] currentThread---<NSThread: 0x600000272fc0>{number = 3, name = (null)}

2018-02-23 20:44:19.377494+0800 YSC-GCD-demo[20083:5040347] syncMain---begin

2018-02-23 20:44:21.384716+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main}

2018-02-23 20:44:23.386091+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main}

2018-02-23 20:44:25.387687+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main}

2018-02-23 20:44:27.388648+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main}

2018-02-23 20:44:29.390459+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main}

2018-02-23 20:44:31.391965+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main}

2018-02-23 20:44:31.392513+0800 YSC-GCD-demo[20083:5040347] syncMain---end

在其他線程中使用同步執(zhí)行 + 主隊(duì)列可看到:

  • 所有任務(wù)都是在主線程(非當(dāng)前線程)中執(zhí)行的允睹,沒有開啟新的線程(所有放在主隊(duì)列中的任務(wù),都會(huì)放到主線程中執(zhí)行)幌氮。

  • 所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)缭受。

  • 任務(wù)是按順序執(zhí)行的(主隊(duì)列是串行隊(duì)列,每次只有一個(gè)任務(wù)被執(zhí)行浩销,任務(wù)一個(gè)接一個(gè)按順序執(zhí)行)贯涎。

為什么現(xiàn)在就不會(huì)卡住了呢?

因?yàn)閟yncMain 任務(wù)放到了其他線程里慢洋,而任務(wù)1塘雳、任務(wù)2、任務(wù)3都在追加到主隊(duì)列中普筹,這三個(gè)任務(wù)都會(huì)在主線程中執(zhí)行败明。syncMain 任務(wù)在其他線程中執(zhí)行到追加任務(wù)1到主隊(duì)列中,因?yàn)橹麝?duì)列現(xiàn)在沒有正在執(zhí)行的任務(wù)太防,所以妻顶,會(huì)直接執(zhí)行主隊(duì)列的任務(wù)1,等任務(wù)1執(zhí)行完畢蜒车,再接著執(zhí)行任務(wù)2讳嘱、任務(wù)3。所以這里不會(huì)卡住線程酿愧。

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

  • 只在主線程中執(zhí)行任務(wù)沥潭,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)嬉挡。

/**

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

  • 特點(diǎn):只在主線程中執(zhí)行任務(wù)钝鸽,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)

*/

  • (void)asyncMain {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"asyncMain---begin");

    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_async(queue, ^{

      // 追加任務(wù)1
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_async(queue, ^{

      // 追加任務(wù)2
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_async(queue, ^{

      // 追加任務(wù)3
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    NSLog(@"asyncMain---end");

}

輸出結(jié)果:

2018-02-23 20:45:49.981505+0800 YSC-GCD-demo[20111:5046708] currentThread---<NSThread: 0x60000006d440>{number = 1, name = main}

2018-02-23 20:45:49.981935+0800 YSC-GCD-demo[20111:5046708] asyncMain---begin

2018-02-23 20:45:49.982352+0800 YSC-GCD-demo[20111:5046708] asyncMain---end

2018-02-23 20:45:51.991096+0800 YSC-GCD-demo[20111:5046708] 1---<NSThread: 0x60000006d440>{number = 1, name = main}

2018-02-23 20:45:53.991959+0800 YSC-GCD-demo[20111:5046708] 1---<NSThread: 0x60000006d440>{number = 1, name = main}

2018-02-23 20:45:55.992937+0800 YSC-GCD-demo[20111:5046708] 2---<NSThread: 0x60000006d440>{number = 1, name = main}

2018-02-23 20:45:57.993649+0800 YSC-GCD-demo[20111:5046708] 2---<NSThread: 0x60000006d440>{number = 1, name = main}

2018-02-23 20:45:59.994928+0800 YSC-GCD-demo[20111:5046708] 3---<NSThread: 0x60000006d440>{number = 1, name = main}

2018-02-23 20:46:01.995589+0800 YSC-GCD-demo[20111:5046708] 3---<NSThread: 0x60000006d440>{number = 1, name = main}

在異步執(zhí)行 + 主隊(duì)列可以看到:

  • 所有任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行的庞钢,并沒有開啟新的線程(雖然異步執(zhí)行具備開啟線程的能力拔恰,但因?yàn)槭侵麝?duì)列,所以所有任務(wù)都在主線程中)基括。

  • 所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的(異步執(zhí)行不會(huì)做任何等待颜懊,可以繼續(xù)執(zhí)行任務(wù))。

  • 任務(wù)是按順序執(zhí)行的(因?yàn)橹麝?duì)列是串行隊(duì)列,每次只有一個(gè)任務(wù)被執(zhí)行河爹,任務(wù)一個(gè)接一個(gè)按順序執(zhí)行)使鹅。

弄懂了難理解、繞來繞去的隊(duì)列+任務(wù)之后昌抠,我們來學(xué)習(xí)一個(gè)簡(jiǎn)單的東西:5. GCD 線程間的通信。

5. GCD 線程間的通信

在iOS開發(fā)過程中鲁僚,我們一般在主線程里邊進(jìn)行UI刷新炊苫,例如:點(diǎn)擊、滾動(dòng)冰沙、拖拽等事件侨艾。我們通常把一些耗時(shí)的操作放在其他線程,比如說圖片下載拓挥、文件上傳等耗時(shí)操作唠梨。而當(dāng)我們有時(shí)候在其他線程完成了耗時(shí)操作時(shí),需要回到主線程侥啤,那么就用到了線程之間的通訊当叭。

/**

  • 線程間通信

*/

  • (void)communication {

    // 獲取全局并發(fā)隊(duì)列

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 獲取主隊(duì)列

    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    dispatch_async(queue, ^{

      // 異步追加任務(wù)
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    
      // 回到主線程
    
      dispatch_async(mainQueue, ^{
    
          // 追加在主線程中執(zhí)行的任務(wù)
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      });
    

    });

}

輸出結(jié)果:

2018-02-23 20:47:03.462394+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}

2018-02-23 20:47:05.465912+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}

2018-02-23 20:47:07.466657+0800 YSC-GCD-demo[20154:5052953] 2---<NSThread: 0x60000007bf80>{number = 1, name = main}

  • 可以看到在其他線程中先執(zhí)行任務(wù),執(zhí)行完了之后回到主線程執(zhí)行主線程的相應(yīng)操作盖灸。

6. GCD 的其他方法

6.1 GCD 柵欄方法:dispatch_barrier_async

  • 我們有時(shí)需要異步執(zhí)行兩組操作蚁鳖,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作赁炎。這樣我們就需要一個(gè)相當(dāng)于柵欄一樣的一個(gè)方法將兩組異步執(zhí)行的操作組給分割起來醉箕,當(dāng)然這里的操作組里可以包含一個(gè)或多個(gè)任務(wù)。這就需要用到dispatch_barrier_async方法在兩個(gè)操作組間形成柵欄徙垫。

    dispatch_barrier_async函數(shù)會(huì)等待前邊追加到并發(fā)隊(duì)列中的任務(wù)全部執(zhí)行完畢之后讥裤,再將指定的任務(wù)追加到該異步隊(duì)列中。然后在dispatch_barrier_async函數(shù)追加的任務(wù)執(zhí)行完畢之后姻报,異步隊(duì)列才恢復(fù)為一般動(dòng)作己英,接著追加任務(wù)到該異步隊(duì)列并開始執(zhí)行。具體如下圖所示:

    image.png

    dispatch_barrier_async.png

/**

  • 柵欄方法 dispatch_barrier_async

*/

  • (void)barrier {

    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];              // 模擬耗時(shí)操作
    
          NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_async(queue, ^{

      // 追加任務(wù)2
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_barrier_async(queue, ^{

      // 追加任務(wù) barrier
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當(dāng)前線程
    
      }
    

    });

    dispatch_async(queue, ^{

      // 追加任務(wù)3
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_async(queue, ^{

      // 追加任務(wù)4
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"4---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

}

輸出結(jié)果:

2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}

2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}

2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}

2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}

2018-02-23 20:48:22.306290+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}

2018-02-23 20:48:24.311655+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}

2018-02-23 20:48:26.316943+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}

2018-02-23 20:48:26.316956+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}

2018-02-23 20:48:28.320660+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}

2018-02-23 20:48:28.320649+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}

在dispatch_barrier_async執(zhí)行結(jié)果中可以看出:

  • 在執(zhí)行完?yáng)艡谇懊娴牟僮髦蠖阂郑艌?zhí)行柵欄操作剧辐,最后再執(zhí)行柵欄后邊的操作。

6.2 GCD 延時(shí)執(zhí)行方法:dispatch_after

我們經(jīng)常會(huì)遇到這樣的需求:在指定時(shí)間(例如3秒)之后執(zhí)行某個(gè)任務(wù)邮府∮兀可以用 GCD 的dispatch_after函數(shù)來實(shí)現(xiàn)。

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

/**

  • 延時(shí)執(zhí)行方法 dispatch_after

*/

  • (void)after {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"asyncMain---begin");

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

      // 2.0秒后異步追加任務(wù)代碼到主隊(duì)列,并開始執(zhí)行
    
      NSLog(@"after---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    

    });

}

輸出結(jié)果:

2018-02-23 20:53:08.713784+0800 YSC-GCD-demo[20282:5080295] currentThread---<NSThread: 0x60000006ee00>{number = 1, name = main}

2018-02-23 20:53:08.713962+0800 YSC-GCD-demo[20282:5080295] asyncMain---begin

2018-02-23 20:53:10.714283+0800 YSC-GCD-demo[20282:5080295] after---<NSThread: 0x60000006ee00>{number = 1, name = main}

可以看出:在打印 asyncMain---begin 之后大約 2.0 秒的時(shí)間未檩,打印了 after---<NSThread: 0x60000006ee00>{number = 1, name = main}

6.3 GCD 一次性代碼(只執(zhí)行一次):dispatch_once

  • 我們?cè)趧?chuàng)建單例戴尸、或者有整個(gè)程序運(yùn)行過程中只執(zhí)行一次的代碼時(shí),我們就用到了 GCD 的 dispatch_once 函數(shù)冤狡。使用

    dispatch_once 函數(shù)能保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次孙蒙,并且即使在多線程的環(huán)境下,dispatch_once也可以保證線程安全悲雳。

/**

  • 一次性代碼(只執(zhí)行一次)dispatch_once

*/

  • (void)once {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

      // 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
    

    });

}

6.4 GCD 快速迭代方法:dispatch_apply

  • 通常我們會(huì)用 for 循環(huán)遍歷挎峦,但是 GCD 給我們提供了快速迭代的函數(shù)dispatch_apply。dispatch_apply按照指定的次數(shù)將指定的任務(wù)追加到指定的隊(duì)列中合瓢,并等待全部隊(duì)列執(zhí)行結(jié)束坦胶。

我們可以利用異步隊(duì)列同時(shí)遍歷。比如說遍歷 0~5 這6個(gè)數(shù)字晴楔,for 循環(huán)的做法是每次取出一個(gè)元素顿苇,逐個(gè)遍歷。dispatch_apply可以同時(shí)遍歷多個(gè)數(shù)字税弃。

/**

  • 快速迭代方法 dispatch_apply

*/

  • (void)apply {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    NSLog(@"apply---begin");

    dispatch_apply(6, queue, ^(size_t index) {

      NSLog(@"%zd---%@",index, [NSThread currentThread]);
    

    });

    NSLog(@"apply---end");

}

輸出結(jié)果:

2018-02-23 22:03:18.475499+0800 YSC-GCD-demo[20470:5176805] apply---begin

2018-02-23 22:03:18.476672+0800 YSC-GCD-demo[20470:5177035] 1---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}

2018-02-23 22:03:18.476693+0800 YSC-GCD-demo[20470:5176805] 0---<NSThread: 0x604000070640>{number = 1, name = main}

2018-02-23 22:03:18.476704+0800 YSC-GCD-demo[20470:5177037] 2---<NSThread: 0x604000276800>{number = 4, name = (null)}

2018-02-23 22:03:18.476735+0800 YSC-GCD-demo[20470:5177036] 3---<NSThread: 0x60000027b800>{number = 5, name = (null)}

2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5177035] 4---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}

2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5176805] 5---<NSThread: 0x604000070640>{number = 1, name = main}

2018-02-23 22:03:18.477038+0800 YSC-GCD-demo[20470:5176805] apply---end

因?yàn)槭窃诓l(fā)隊(duì)列中異步隊(duì)執(zhí)行任務(wù)岖圈,所以各個(gè)任務(wù)的執(zhí)行時(shí)間長(zhǎng)短不定,最后結(jié)束順序也不定钙皮。但是apply---end一定在最后執(zhí)行蜂科。這是因?yàn)閐ispatch_apply函數(shù)會(huì)等待全部任務(wù)執(zhí)行完畢。

6.5 GCD 隊(duì)列組:dispatch_group

有時(shí)候我們會(huì)有這樣的需求:分別異步執(zhí)行2個(gè)耗時(shí)任務(wù)短条,然后當(dāng)2個(gè)耗時(shí)任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù)导匣。這時(shí)候我們可以用到 GCD 的隊(duì)列組。

  • 調(diào)用隊(duì)列組的 dispatch_group_async 先把任務(wù)放到隊(duì)列中茸时,然后將隊(duì)列放入隊(duì)列組中贡定。或者使用隊(duì)列組的 dispatch_group_enter可都、dispatch_group_leave 組合 來實(shí)現(xiàn)

    dispatch_group_async缓待。

  • 調(diào)用隊(duì)列組的 dispatch_group_notify 回到指定線程執(zhí)行任務(wù)∏或者使用 dispatch_group_wait 回到當(dāng)前線程繼續(xù)向下執(zhí)行(會(huì)阻塞當(dāng)前線程)旋炒。

6.5.1 dispatch_group_notify

  • 監(jiān)聽 group 中任務(wù)的完成狀態(tài),當(dāng)所有的任務(wù)都執(zhí)行完成后签杈,追加任務(wù)到 group 中瘫镇,并執(zhí)行任務(wù)。

/**

  • 隊(duì)列組 dispatch_group_notify

*/

  • (void)groupNotify {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"group---begin");

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

      // 追加任務(wù)1
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

      // 追加任務(wù)2
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

      // 等前面的異步任務(wù)1、任務(wù)2都執(zhí)行完畢后铣除,回到主線程執(zhí)行下邊任務(wù)
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    
      NSLog(@"group---end");
    

    });

}

輸出結(jié)果:

2018-02-23 22:05:03.790035+0800 YSC-GCD-demo[20494:5183349] currentThread---<NSThread: 0x604000072040>{number = 1, name = main}

2018-02-23 22:05:03.790237+0800 YSC-GCD-demo[20494:5183349] group---begin

2018-02-23 22:05:05.792721+0800 YSC-GCD-demo[20494:5183654] 1---<NSThread: 0x60000026f280>{number = 4, name = (null)}

2018-02-23 22:05:05.792725+0800 YSC-GCD-demo[20494:5183656] 2---<NSThread: 0x60000026f240>{number = 3, name = (null)}

2018-02-23 22:05:07.797408+0800 YSC-GCD-demo[20494:5183656] 2---<NSThread: 0x60000026f240>{number = 3, name = (null)}

2018-02-23 22:05:07.797408+0800 YSC-GCD-demo[20494:5183654] 1---<NSThread: 0x60000026f280>{number = 4, name = (null)}

2018-02-23 22:05:09.798717+0800 YSC-GCD-demo[20494:5183349] 3---<NSThread: 0x604000072040>{number = 1, name = main}

2018-02-23 22:05:11.799827+0800 YSC-GCD-demo[20494:5183349] 3---<NSThread: 0x604000072040>{number = 1, name = main}

2018-02-23 22:05:11.799977+0800 YSC-GCD-demo[20494:5183349] group---end

從dispatch_group_notify相關(guān)代碼運(yùn)行輸出結(jié)果可以看出:

當(dāng)所有任務(wù)都執(zhí)行完成之后谚咬,才執(zhí)行dispatch_group_notify block 中的任務(wù)。

6.5.2 dispatch_group_wait

  • 暫停當(dāng)前線程(阻塞當(dāng)前線程)尚粘,等待指定的 group 中的任務(wù)執(zhí)行完成后择卦,才會(huì)往下繼續(xù)執(zhí)行。

/**

  • 隊(duì)列組 dispatch_group_wait

*/

  • (void)groupWait {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"group---begin");

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

      // 追加任務(wù)1
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

      // 追加任務(wù)2
    
      for (int i = 0; i < 2; ++i) {
    
          [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
          NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      }
    

    });

    // 等待上面的任務(wù)全部完成后郎嫁,會(huì)往下繼續(xù)執(zhí)行(會(huì)阻塞當(dāng)前線程)

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    NSLog(@"group---end");

}

輸出結(jié)果:

2018-02-23 22:10:16.939258+0800 YSC-GCD-demo[20538:5198871] currentThread---<NSThread: 0x600000066780>{number = 1, name = main}

2018-02-23 22:10:16.939455+0800 YSC-GCD-demo[20538:5198871] group---begin

2018-02-23 22:10:18.943862+0800 YSC-GCD-demo[20538:5199137] 2---<NSThread: 0x600000464b80>{number = 4, name = (null)}

2018-02-23 22:10:18.943861+0800 YSC-GCD-demo[20538:5199138] 1---<NSThread: 0x604000076640>{number = 3, name = (null)}

2018-02-23 22:10:20.947787+0800 YSC-GCD-demo[20538:5199137] 2---<NSThread: 0x600000464b80>{number = 4, name = (null)}

2018-02-23 22:10:20.947790+0800 YSC-GCD-demo[20538:5199138] 1---<NSThread: 0x604000076640>{number = 3, name = (null)}

2018-02-23 22:10:20.948134+0800 YSC-GCD-demo[20538:5198871] group---end

從dispatch_group_wait相關(guān)代碼運(yùn)行輸出結(jié)果可以看出:

當(dāng)所有任務(wù)執(zhí)行完成之后互捌,才執(zhí)行 dispatch_group_wait 之后的操作。但是行剂,使用dispatch_group_wait 會(huì)阻塞當(dāng)前線程。

6.5.3 dispatch_group_enter钳降、dispatch_group_leave

  • 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ù)。

/**

  • 隊(duì)列組 dispatch_group_enter礁击、dispatch_group_leave

*/

  • (void)groupEnterAndLeave

{

NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當(dāng)前線程

NSLog(@"group---begin");

dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_enter(group);

dispatch_async(queue, ^{

    // 追加任務(wù)1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程

    }

    dispatch_group_leave(group);

});

dispatch_group_enter(group);

dispatch_async(queue, ^{

    // 追加任務(wù)2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程

    }

    dispatch_group_leave(group);

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    // 等前面的異步操作都執(zhí)行完畢后盐杂,回到主線程.

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作

        NSLog(@"3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程

    }

    NSLog(@"group---end");

});

// // 等待上面的任務(wù)全部完成后,會(huì)往下繼續(xù)執(zhí)行(會(huì)阻塞當(dāng)前線程)

// dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

//

// NSLog(@"group---end");

}

輸出結(jié)果:

2018-02-23 22:14:17.997667+0800 YSC-GCD-demo[20592:5214830] currentThread---<NSThread: 0x604000066600>{number = 1, name = main}

2018-02-23 22:14:17.997839+0800 YSC-GCD-demo[20592:5214830] group---begin

2018-02-23 22:14:20.000298+0800 YSC-GCD-demo[20592:5215094] 1---<NSThread: 0x600000277c80>{number = 4, name = (null)}

2018-02-23 22:14:20.000305+0800 YSC-GCD-demo[20592:5215095] 2---<NSThread: 0x600000277c40>{number = 3, name = (null)}

2018-02-23 22:14:22.001323+0800 YSC-GCD-demo[20592:5215094] 1---<NSThread: 0x600000277c80>{number = 4, name = (null)}

2018-02-23 22:14:22.001339+0800 YSC-GCD-demo[20592:5215095] 2---<NSThread: 0x600000277c40>{number = 3, name = (null)}

2018-02-23 22:14:24.002321+0800 YSC-GCD-demo[20592:5214830] 3---<NSThread: 0x604000066600>{number = 1, name = main}

2018-02-23 22:14:26.002852+0800 YSC-GCD-demo[20592:5214830] 3---<NSThread: 0x604000066600>{number = 1, name = main}

2018-02-23 22:14:26.003116+0800 YSC-GCD-demo[20592:5214830] group---end

從dispatch_group_enter哆窿、dispatch_group_leave相關(guān)代碼運(yùn)行結(jié)果中可以看出:當(dāng)所有任務(wù)執(zhí)行完成之后链烈,才執(zhí)行 dispatch_group_notify 中的任務(wù)。這里的dispatch_group_enter挚躯、dispatch_group_leave組合强衡,其實(shí)等同于dispatch_group_async。

6.6 GCD 信號(hào)量:dispatch_semaphore

GCD 中的信號(hào)量是指 Dispatch Semaphore码荔,是持有計(jì)數(shù)的信號(hào)漩勤。類似于過高速路收費(fèi)站的欄桿∷踅粒可以通過時(shí)越败,打開欄桿,不可以通過時(shí)硼瓣,關(guān)閉欄桿眉尸。在 Dispatch Semaphore 中,使用計(jì)數(shù)來完成這個(gè)功能,計(jì)數(shù)為0時(shí)等待噪猾,不可通過霉祸。計(jì)數(shù)為1或大于1時(shí),計(jì)數(shù)減1且不等待袱蜡,可通過丝蹭。

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í)行敏晤。

注意:信號(hào)量的使用前提是:想清楚你需要處理哪個(gè)線程等待(阻塞)贱田,又要哪個(gè)線程繼續(xù)執(zhí)行,然后使用信號(hào)量嘴脾。

Dispatch Semaphore 在實(shí)際開發(fā)中主要用于:

  • 保持線程同步男摧,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)

  • 保證線程安全,為線程加鎖

6.6.1 Dispatch Semaphore 線程同步

我們?cè)陂_發(fā)中译打,會(huì)遇到這樣的需求:異步執(zhí)行耗時(shí)任務(wù)耗拓,并使用異步執(zhí)行的結(jié)果進(jìn)行一些額外的操作。換句話說奏司,相當(dāng)于乔询,將將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)。比如說:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法韵洋。通過引入信號(hào)量的方式竿刁,等待異步執(zhí)行任務(wù)結(jié)果,獲取到 tasks搪缨,然后再返回該 tasks们妥。

  • (NSArray *)tasksForKeyPath:(NSString *)keyPath {

    __block NSArray *tasks = nil;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {

      if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
    
          tasks = dataTasks;
    
      } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
    
          tasks = uploadTasks;
    
      } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
    
          tasks = downloadTasks;
    
      } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
    
          tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
    
      }
    
      dispatch_semaphore_signal(semaphore);
    

    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;

}

下面,我們來利用 Dispatch Semaphore 實(shí)現(xiàn)線程同步勉吻,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)监婶。

/**

  • semaphore 線程同步

*/

  • (void)semaphoreSync {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"semaphore---begin");

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    __block int number = 0;

    dispatch_async(queue, ^{

      // 追加任務(wù)1
    
      [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
    
      NSLog(@"1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    
      number = 100;
    
      dispatch_semaphore_signal(semaphore);
    

    });

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    NSLog(@"semaphore---end,number = %zd",number);

}

輸出結(jié)果:

2018-02-23 22:22:26.521665+0800 YSC-GCD-demo[20642:5246341] currentThread---<NSThread: 0x60400006bc80>{number = 1, name = main}

2018-02-23 22:22:26.521869+0800 YSC-GCD-demo[20642:5246341] semaphore---begin

2018-02-23 22:22:28.526841+0800 YSC-GCD-demo[20642:5246638] 1---<NSThread: 0x600000272300>{number = 3, name = (null)}

2018-02-23 22:22:28.527030+0800 YSC-GCD-demo[20642:5246341] semaphore---end,number = 100

從 Dispatch Semaphore 實(shí)現(xiàn)線程同步的代碼可以看到:

  • semaphore---end 是在執(zhí)行完 number = 100; 之后才打印的。而且輸出結(jié)果 number 為 100齿桃。

    這是因?yàn)楫惒綀?zhí)行不會(huì)做任何等待惑惶,可以繼續(xù)執(zhí)行任務(wù)。異步執(zhí)行將任務(wù)1追加到隊(duì)列之后短纵,不做等待带污,接著執(zhí)行dispatch_semaphore_wait方法。此時(shí) semaphore == 0香到,當(dāng)前線程進(jìn)入等待狀態(tài)鱼冀。然后报破,異步任務(wù)1開始執(zhí)行。任務(wù)1執(zhí)行到dispatch_semaphore_signal之后千绪,總信號(hào)量充易,此時(shí) semaphore == 1,dispatch_semaphore_wait方法使總信號(hào)量減1荸型,正在被阻塞的線程(主線程)恢復(fù)繼續(xù)執(zhí)行盹靴。最后打印semaphore---end,number = 100。這樣就實(shí)現(xiàn)了線程同步瑞妇,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)稿静。

6.6.2 Dispatch Semaphore 線程安全和線程同步(為線程加鎖)

線程安全:如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼辕狰。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的改备,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的蔓倍。

若每個(gè)線程中對(duì)全局變量悬钳、靜態(tài)變量只有讀操作,而無寫操作柬脸,一般來說,這個(gè)全局變量是線程安全的毙驯;若有多個(gè)線程同時(shí)執(zhí)行寫操作(更改變量)倒堕,一般都需要考慮線程同步,否則的話就可能影響線程安全爆价。

線程同步:可理解為線程 A 和 線程 B 一塊配合垦巴,A 執(zhí)行到一定程度時(shí)要依靠線程 B 的某個(gè)結(jié)果,于是停下來铭段,示意 B 運(yùn)行骤宣;B 依言執(zhí)行,再將結(jié)果給 A序愚;A 再繼續(xù)操作憔披。

舉個(gè)簡(jiǎn)單例子就是:兩個(gè)人在一起聊天。兩個(gè)人不能同時(shí)說話爸吮,避免聽不清(操作沖突)芬膝。等一個(gè)人說完(一個(gè)線程結(jié)束操作),另一個(gè)再說(另一個(gè)線程再開始操作)形娇。

下面锰霜,我們模擬火車票售賣的方式,實(shí)現(xiàn) NSThread 線程安全和解決線程同步問題桐早。

場(chǎng)景:總共有50張火車票癣缅,有兩個(gè)售賣火車票的窗口厨剪,一個(gè)是北京火車票售賣窗口,另一個(gè)是上河汛妫火車票售賣窗口祷膳。兩個(gè)窗口同時(shí)售賣火車票,賣完為止爬立。

6.6.2.1 非線程安全(不使用 semaphore)

先來看看不考慮線程安全的代碼:

/**

  • 非線程安全:不使用 semaphore

  • 初始化火車票數(shù)量钾唬、賣票窗口(非線程安全)、并開始賣票

*/

  • (void)initTicketStatusNotSave {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"semaphore---begin");

    self.ticketSurplusCount = 50;

    // queue1 代表北京火車票售賣窗口

    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);

    // queue2 代表上合姥保火車票售賣窗口

    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);

    __weak typeof(self) weakSelf = self;

    dispatch_async(queue1, ^{

      [weakSelf saleTicketNotSafe];
    

    });

    dispatch_async(queue2, ^{

      [weakSelf saleTicketNotSafe];
    

    });

}

/**

  • 售賣火車票(非線程安全)

*/

  • (void)saleTicketNotSafe {

    while (1) {

      if (self.ticketSurplusCount > 0) {  //如果還有票抡秆,繼續(xù)售賣
    
          self.ticketSurplusCount--;
    
          NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
    
          [NSThread sleepForTimeInterval:0.2];
    
      } else { //如果已賣完,關(guān)閉售票窗口
    
          NSLog(@"所有火車票均已售完");
    
          break;
    
      }
    

    }

}

輸出結(jié)果(部分):

2018-02-23 22:25:35.789072+0800 YSC-GCD-demo[20712:5258914] currentThread---<NSThread: 0x604000068880>{number = 1, name = main}

2018-02-23 22:25:35.789260+0800 YSC-GCD-demo[20712:5258914] semaphore---begin

2018-02-23 22:25:35.789641+0800 YSC-GCD-demo[20712:5259176] 剩余票數(shù):48 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}

2018-02-23 22:25:35.789646+0800 YSC-GCD-demo[20712:5259175] 剩余票數(shù):49 窗口:<NSThread: 0x60000027e740>{number = 4, name = (null)}

2018-02-23 22:25:35.994113+0800 YSC-GCD-demo[20712:5259175] 剩余票數(shù):47 窗口:<NSThread: 0x60000027e740>{number = 4, name = (null)}

2018-02-23 22:25:35.994129+0800 YSC-GCD-demo[20712:5259176] 剩余票數(shù):46 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}

2018-02-23 22:25:36.198993+0800 YSC-GCD-demo[20712:5259176] 剩余票數(shù):45 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}

...

可以看到在不考慮線程安全吟策,不使用 semaphore 的情況下儒士,得到票數(shù)是錯(cuò)亂的,這樣顯然不符合我們的需求檩坚,所以我們需要考慮線程安全問題着撩。

6.6.2.2 線程安全(使用 semaphore 加鎖)

考慮線程安全的代碼:

/**

  • 線程安全:使用 semaphore 加鎖

  • 初始化火車票數(shù)量、賣票窗口(線程安全)匾委、并開始賣票

*/

  • (void)initTicketStatusSave {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程

    NSLog(@"semaphore---begin");

    semaphoreLock = dispatch_semaphore_create(1);

    self.ticketSurplusCount = 50;

    // queue1 代表北京火車票售賣窗口

    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);

    // queue2 代表上和闲穑火車票售賣窗口

    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);

    __weak typeof(self) weakSelf = self;

    dispatch_async(queue1, ^{

      [weakSelf saleTicketSafe];
    

    });

    dispatch_async(queue2, ^{

      [weakSelf saleTicketSafe];
    

    });

}

/**

  • 售賣火車票(線程安全)

*/

  • (void)saleTicketSafe {

    while (1) {

      // 相當(dāng)于加鎖
    
      dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
    
      if (self.ticketSurplusCount > 0) {  //如果還有票相寇,繼續(xù)售賣
    
          self.ticketSurplusCount--;
    
          NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
    
          [NSThread sleepForTimeInterval:0.2];
    
      } else { //如果已賣完绽媒,關(guān)閉售票窗口
    
          NSLog(@"所有火車票均已售完");
    
          // 相當(dāng)于解鎖
    
          dispatch_semaphore_signal(semaphoreLock);
    
          break;
    
      }
    
      // 相當(dāng)于解鎖
    
      dispatch_semaphore_signal(semaphoreLock);
    

    }

}

輸出結(jié)果為:

2018-02-23 22:32:19.814232+0800 YSC-GCD-demo[20862:5290531] currentThread---<NSThread: 0x6000000783c0>{number = 1, name = main}

2018-02-23 22:32:19.814412+0800 YSC-GCD-demo[20862:5290531] semaphore---begin

2018-02-23 22:32:19.814837+0800 YSC-GCD-demo[20862:5290687] 剩余票數(shù):49 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}

2018-02-23 22:32:20.017745+0800 YSC-GCD-demo[20862:5290689] 剩余票數(shù):48 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}

2018-02-23 22:32:20.222039+0800 YSC-GCD-demo[20862:5290687] 剩余票數(shù):47 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}

...

2018-02-23 22:32:29.024817+0800 YSC-GCD-demo[20862:5290689] 剩余票數(shù):4 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}

2018-02-23 22:32:29.230110+0800 YSC-GCD-demo[20862:5290687] 剩余票數(shù):3 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}

2018-02-23 22:32:29.433615+0800 YSC-GCD-demo[20862:5290689] 剩余票數(shù):2 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}

2018-02-23 22:32:29.637572+0800 YSC-GCD-demo[20862:5290687] 剩余票數(shù):1 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}

2018-02-23 22:32:29.840234+0800 YSC-GCD-demo[20862:5290689] 剩余票數(shù):0 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}

2018-02-23 22:32:30.044960+0800 YSC-GCD-demo[20862:5290687] 所有火車票均已售完

2018-02-23 22:32:30.045260+0800 YSC-GCD-demo[20862:5290689] 所有火車票均已售完

可以看出痴柔,在考慮了線程安全的情況下颜骤,使用 dispatch_semaphore

機(jī)制之后妙黍,得到的票數(shù)是正確的锣夹,沒有出現(xiàn)混亂的情況浪南。我們也就解決了多個(gè)線程同步的問題父款。


參考資料:



iOS多線程詳盡總結(jié)系列文章:


  • 本文作者: 行走的少年郎
  • 版權(quán)聲明: 本文章采用 CC BY-NC-SA 3.0 許可協(xié)議斩松。轉(zhuǎn)載請(qǐng)注明出處!

iOS 開發(fā)

? 著作權(quán)歸作者所有

舉報(bào)文章

關(guān)注

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末觉既,一起剝皮案震驚了整個(gè)濱河市惧盹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瞪讼,老刑警劉巖岭参,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異尝艘,居然都是意外死亡演侯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門背亥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秒际,“玉大人悬赏,你說我怎么就攤上這事÷玻” “怎么了闽颇?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)寄锐。 經(jīng)常有香客問我兵多,道長(zhǎng),這世上最難降的妖魔是什么橄仆? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任剩膘,我火速辦了婚禮,結(jié)果婚禮上盆顾,老公的妹妹穿的比我還像新娘怠褐。我一直安慰自己,他們只是感情好您宪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布奈懒。 她就那樣靜靜地躺著,像睡著了一般宪巨。 火紅的嫁衣襯著肌膚如雪磷杏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天捏卓,我揣著相機(jī)與錄音极祸,去河邊找鬼。 笑死天吓,一個(gè)胖子當(dāng)著我的面吹牛贿肩,可吹牛的內(nèi)容都是我干的峦椰。 我是一名探鬼主播龄寞,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼汤功!你這毒婦竟也來了物邑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤滔金,失蹤者是張志新(化名)和其女友劉穎色解,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體餐茵,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡科阎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忿族。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锣笨。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蝌矛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出错英,到底是詐尸還是另有隱情入撒,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布椭岩,位于F島的核電站茅逮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏判哥。R本人自食惡果不足惜献雅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望姨伟。 院中可真熱鬧惩琉,春花似錦、人聲如沸夺荒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)技扼。三九已至伍玖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剿吻,已是汗流浹背窍箍。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丽旅,地道東北人椰棘。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像榄笙,于是被迫代替她去往敵國(guó)和親邪狞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容