iOS多線程編程(四) GCD

多線程系列篇章計(jì)劃內(nèi)容:
iOS多線程編程(一) 多線程基礎(chǔ)
iOS多線程編程(二) Pthread
iOS多線程編程(三) NSThread
iOS多線程編程(四) GCD
iOS多線程編程(五) GCD的底層原理
iOS多線程編程(六) NSOperation
iOS多線程編程(七) 同步機(jī)制與鎖
iOS多線程編程(八) RunLoop

前言

本文主要介紹GCD相關(guān)概念以及使用立砸,對(duì)于GCD的核心概念伏穆、函數(shù)和隊(duì)列的搭配使用啡省、函數(shù)和隊(duì)列的復(fù)雜組合示例以及GCD中的線程同步機(jī)制做了詳細(xì)的分析奋献。未做底層源碼分析盖腿,若想了解GCD底層原理分析坊饶,可移步 iOS多線程編程(五) GCD的底層原理棱烂。

附:iOS下的多線程方案:

1. GCD簡(jiǎn)介

GCD 全稱(chēng) Grand Central Dispatch鱼的,基于C語(yǔ)言實(shí)現(xiàn)的多線程機(jī)制占遥,是Apple提供的一個(gè)多核編程的解決方案俯抖。它允許將一個(gè)程序切分為多個(gè)單一任務(wù),然后提交到工作隊(duì)列中并發(fā)或串行地執(zhí)行瓦胎。在 Mac OS X 10.6 雪豹中首次推出芬萍,也可在 iOS 4 及以上版本使用。

他的優(yōu)勢(shì)體現(xiàn)在:

  • GCD 可用于多核的并行運(yùn)算搔啊,會(huì)自動(dòng)合理地利用 CPU內(nèi)核(比如雙核柬祠、四核)。
  • GCD 使用簡(jiǎn)單负芋,開(kāi)發(fā)者要做的只是定義執(zhí)行的 任務(wù)漫蛔,追加到適當(dāng)?shù)?隊(duì)列 中,并且指定執(zhí)行任務(wù)的 函數(shù)旧蛾。配合Block莽龟,使用起來(lái)也更加方便靈活。
  • GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程锨天、調(diào)度任務(wù)毯盈、銷(xiāo)毀線程)。

2. GCD的核心概念

在開(kāi)發(fā)中病袄,我們通常是這樣使用GCD的搂赋。

dispatch_async(dispatch_queue_create("com.xxx.testqueue", DISPATCH_QUEUE_CONCURRENT), ^{
    NSLog(@"gcd test");
});

GCD使用的本質(zhì),就是 “定義要執(zhí)行的『任務(wù)』益缠,將任務(wù)添加到『隊(duì)列』中脑奠,并且指定執(zhí)行任務(wù)的『函數(shù)』”。怎么理解這句話(huà)呢幅慌?不妨將上面的示例代碼拆分開(kāi)來(lái):

// 1.定義要執(zhí)行的任務(wù)(打印 “gcd test”)
dispatch_block_t task = ^{
    NSLog(@"gcd test");
};
// 2.指定任務(wù)的目標(biāo)隊(duì)列(并發(fā)隊(duì)列)
dispatch_queue_t queue = dispatch_queue_create("com.xxx.testqueue", DISPATCH_QUEUE_CONCURRENT);
// 3.使用異步函數(shù)宋欺,將任務(wù)提交到目標(biāo)隊(duì)列
dispatch_async(queue, task);

這樣就比較清晰了,對(duì)于GCD的使用有三個(gè)要素:『任務(wù)』、『隊(duì)列』和 『函數(shù)』迄靠。

2.1 任務(wù)

任務(wù)就是要執(zhí)行的操作喇辽,在GCD中也就是在Block內(nèi)的代碼段掌挚,任務(wù)的Block沒(méi)有參數(shù)也沒(méi)有返回值。

2.2 函數(shù)

函數(shù)決定了任務(wù)的執(zhí)行方式菩咨》褪剑『同步執(zhí)行』 還是 『異步執(zhí)行』。兩者的主要區(qū)別是:是否需要等待當(dāng)前任務(wù)的返回結(jié)果抽米,以及是否具備開(kāi)啟新線程的能力特占。

2.2.1 同步函數(shù)(sync)

  • 同步函數(shù)執(zhí)行的任務(wù),調(diào)用一旦開(kāi)始云茸,調(diào)用者必須等待任務(wù)執(zhí)行完畢是目,才能繼續(xù)執(zhí)行后續(xù)行為。
  • 不具備開(kāi)啟新線程的能力(任務(wù)只能在當(dāng)前線程中執(zhí)行任務(wù))标捺。

2.2.2 異步函數(shù)(async)

  • 異步函數(shù)執(zhí)行的任務(wù)懊纳,調(diào)用者無(wú)需等待任務(wù)執(zhí)行完畢,就可以繼續(xù)執(zhí)行后續(xù)行為亡容。

  • 具備開(kāi)啟新線程的能力(可以在新的線程中執(zhí)行任務(wù))嗤疯。

需要注意的是:異步執(zhí)行雖然具有開(kāi)啟新線程的能力,但并不一定要開(kāi)啟新線程闺兢,還與任務(wù)所屬隊(duì)列有關(guān)(如異步執(zhí)行主隊(duì)列中的任務(wù)就不會(huì)開(kāi)啟新線程)。

通常,我們也將同步執(zhí)行的任務(wù)記為同步任務(wù)减响,異步執(zhí)行的任務(wù)記為異步任務(wù)转晰。

2.3 隊(duì)列

隊(duì)列是一種特殊的線性表,它只允許在表的前端進(jìn)行刪除操作桐磁,在表的后端進(jìn)行插入操作悔耘。所以只有最早進(jìn)入隊(duì)列的元素才能最先從隊(duì)列中刪除。因此隊(duì)列的基本特性就是 FIFO(先進(jìn)先出) 原則所意。

在GCD中淮逊,我們需要將任務(wù)添加到隊(duì)列中,新任務(wù)總是被插入到隊(duì)列的末尾扶踊,而調(diào)度任務(wù)的時(shí)候總是從隊(duì)列的頭部開(kāi)始執(zhí)行泄鹏。每調(diào)度一個(gè)任務(wù),則從隊(duì)列中移除該任務(wù)秧耗。

在GCD中有兩種隊(duì)列:串行隊(duì)列并發(fā)隊(duì)列备籽。兩者都遵循FIFO原則,兩者的主要區(qū)別是:執(zhí)行順序不同,使用的線程個(gè)數(shù)不同车猬。

2.3.1 串行隊(duì)列

串行隊(duì)列每次只有一個(gè)任務(wù)被調(diào)度霉猛,任務(wù)一個(gè)接一個(gè)地執(zhí)行,且任務(wù)都在同一個(gè)線程執(zhí)行珠闰。只有任務(wù)1被調(diào)度完畢才能調(diào)度任務(wù)2惜浅,以此類(lèi)推。

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

并發(fā)隊(duì)列可以讓多個(gè)任務(wù)并發(fā)(”同時(shí)“)執(zhí)行伏嗜。這取決于有多少可以利用的線程坛悉,假設(shè)在兩條線程可用的情況下,那么任務(wù)1和任務(wù)2可分別在不同線程中并發(fā)執(zhí)行承绸。

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

如果從執(zhí)行時(shí)間上對(duì)比兩者的區(qū)別:那么同一時(shí)刻,串行隊(duì)列只有一個(gè)任務(wù)在執(zhí)行军熏,而并發(fā)隊(duì)列在同一時(shí)刻可能有多個(gè)任務(wù)在執(zhí)行轩猩,并且,哪個(gè)任務(wù)先執(zhí)行完畢也是不確定的(這受任務(wù)復(fù)雜度以及CPU調(diào)度的影響)荡澎。

如上圖均践,在并發(fā)隊(duì)列中,紅線位置表示衔瓮,同一時(shí)間任務(wù)2浊猾、3、4都在執(zhí)行热鞍,并且任務(wù)4先于任務(wù)3調(diào)度(CPU的調(diào)度)葫慎,但是任務(wù)3的復(fù)雜度較低,所以任務(wù)3先于任務(wù)4執(zhí)行完畢(任務(wù)復(fù)雜度)薇宠。而對(duì)于串行隊(duì)列偷办,同一時(shí)間只能有一個(gè)任務(wù)在執(zhí)行,并且任務(wù)嚴(yán)格按照隊(duì)列中的順序執(zhí)行澄港。

更為嚴(yán)謹(jǐn)?shù)恼f(shuō)法椒涯,就要考慮并發(fā)并行的區(qū)別了,并行是多核下真正的同時(shí)執(zhí)行回梧,而并發(fā)則是由CPU時(shí)間片的輪轉(zhuǎn)機(jī)制废岂,讓我們看起來(lái)好像是同時(shí)執(zhí)行一樣。從宏觀上狱意,我們可以將兩者看成是一回事湖苞,因?yàn)镃PU的時(shí)間粒度實(shí)在是太小了。

2.3.3 主隊(duì)列

主隊(duì)列是一種特殊的 串行隊(duì)列详囤,在libdispatch_init 初始化時(shí)就創(chuàng)建了主隊(duì)列财骨,并且完成了與主線程的綁定。這些都是在程序main()函數(shù)之前就已完成的。

也就是說(shuō)程序完成啟動(dòng)之時(shí)就已經(jīng)有了主隊(duì)列隆箩,并且所有放在主隊(duì)列中的任務(wù)都是在主線程中執(zhí)行的该贾。不管是同步還是異步都不會(huì)開(kāi)辟新線程,任務(wù)只會(huì)在主線程執(zhí)行捌臊。這也是通常在主線程刷新UI時(shí)會(huì)將任務(wù)放到主隊(duì)列的原因杨蛋。

可通過(guò)dispatch_get_main_queue()獲取主隊(duì)列。

2.3.4 全局并發(fā)隊(duì)列

全局并發(fā)隊(duì)列 本質(zhì)上是一個(gè)并發(fā)隊(duì)列娃属,由系統(tǒng)提供六荒,方便編程护姆,不用創(chuàng)建就可使用矾端。
可通過(guò)dispatch_get_global_queue(long indentifier.unsigned long flags)獲取全局并發(fā)隊(duì)列。
該函數(shù)提供了兩個(gè)參數(shù)卵皂,第一個(gè)參數(shù)表示隊(duì)列優(yōu)先級(jí)秩铆,通常寫(xiě)0,也就是默認(rèn)優(yōu)先級(jí)灯变∨孤辏可以通過(guò)服務(wù)質(zhì)量類(lèi)值來(lái)獲取不同優(yōu)先級(jí)的全局并發(fā)隊(duì)列。

 *  - QOS_CLASS_USER_INITIATED
 *  - QOS_CLASS_DEFAULT
 *  - QOS_CLASS_UTILITY
 *  - QOS_CLASS_BACKGROUND

也可以通過(guò)隊(duì)列的優(yōu)先級(jí)來(lái)識(shí)別添祸,他們的映射關(guān)系如下:

 *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
 *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
 *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
 *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND

第二個(gè)參數(shù)為預(yù)留參數(shù)滚粟。建議寫(xiě)0,因?yàn)锳pple說(shuō)刃泌,傳遞除零之外的任何值都可能導(dǎo)致空返回值凡壤。


關(guān)于隊(duì)列任務(wù)線程耙替,有篇文章的類(lèi)比的描述的比較好理解亚侠,復(fù)制供大家理解:

假設(shè)現(xiàn)在有 5 個(gè)人要穿過(guò)一道門(mén)禁,這道門(mén)禁總共有 10 個(gè)入口俗扇,管理員可以決定同一時(shí)間打開(kāi)幾個(gè)入口硝烂,可以決定同一時(shí)間讓一個(gè)人單獨(dú)通過(guò)還是多個(gè)人一起通過(guò)。不過(guò)默認(rèn)情況下铜幽,管理員只開(kāi)啟一個(gè)入口滞谢,且一個(gè)通道一次只能通過(guò)一個(gè)人。

這個(gè)故事里除抛,人好比是 任務(wù)狮杨,管理員好比是 系統(tǒng),入口則代表 線程镶殷。

  • 5 個(gè)人表示有 5 個(gè)任務(wù)禾酱,10 個(gè)入口代表 10 條線程。
  • 串行隊(duì)列 好比是 5 個(gè)人排成一支長(zhǎng)隊(duì)。
  • 并發(fā)隊(duì)列 好比是 5 個(gè)人排成多支隊(duì)伍颤陶,比如 2 隊(duì)颗管,或者 3 隊(duì)。
  • 同步任務(wù) 好比是管理員只開(kāi)啟了一個(gè)入口(當(dāng)前線程)滓走。
  • 異步任務(wù) 好比是管理員同時(shí)開(kāi)啟了多個(gè)入口(當(dāng)前線程 + 新開(kāi)的線程)垦江。

『異步執(zhí)行 + 并發(fā)隊(duì)列』 可以理解為:現(xiàn)在管理員開(kāi)啟了多個(gè)入口(比如 3 個(gè)入口),5 個(gè)人排成了多支隊(duì)伍(比如 3 支隊(duì)伍)搅方,這樣這 5 個(gè)人就可以 3 個(gè)人同時(shí)一起穿過(guò)門(mén)禁了比吭。
『同步執(zhí)行 + 并發(fā)隊(duì)列』 可以理解為:現(xiàn)在管理員只開(kāi)啟了 1 個(gè)入口,5 個(gè)人排成了多支隊(duì)伍姨涡。雖然這 5 個(gè)人排成了多支隊(duì)伍衩藤,但是只開(kāi)了 1 個(gè)入口啊,這 5 個(gè)人雖然都想快點(diǎn)過(guò)去涛漂,但是 1 個(gè)入口一次只能過(guò) 1 個(gè)人赏表,所以大家就只好一個(gè)接一個(gè)走過(guò)去了,表現(xiàn)的結(jié)果就是:順次通過(guò)入口匈仗。

換成 GCD 里的語(yǔ)言就是說(shuō):

  • 異步執(zhí)行 + 并發(fā)隊(duì)列』就是:系統(tǒng)開(kāi)啟了多個(gè)線程(主線程+其他子線程)瓢剿,任務(wù)可以多個(gè)同時(shí)運(yùn)行。
  • 同步執(zhí)行 + 并發(fā)隊(duì)列』就是:系統(tǒng)只默認(rèn)開(kāi)啟了一個(gè)主線程悠轩,沒(méi)有開(kāi)啟子線程间狂,雖然任務(wù)處于并發(fā)隊(duì)列中,但也只能一個(gè)接一個(gè)執(zhí)行了火架。

3. 隊(duì)列與函數(shù)的搭配

GCD中有兩種隊(duì)列(串行/并發(fā))鉴象,兩種任務(wù)執(zhí)行方式(同步/異步),那么自然就有這樣的四種組合使用方式:

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

全局并發(fā)隊(duì)列和我們自定義的普通并發(fā)隊(duì)列關(guān)于同步/異步執(zhí)行結(jié)果是相同的距潘。而對(duì)于主隊(duì)列炼列,它是一種特殊的串行隊(duì)列,只要在主隊(duì)列中的任務(wù)一定會(huì)在主線程中執(zhí)行音比。我們將主隊(duì)列也考慮其中俭尖,這樣就有六種組合使用方式:

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

接下來(lái)我們探討這幾種不同組合的區(qū)別有哪些?

3.1 同步+串行

//  同步+串行 任務(wù)
- (void)sync_serial{

    // 打印當(dāng)前線程
    NSLog(@"currentThread---%@",[NSThread currentThread]);  
    NSLog(@"begin");
    
    // 串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
    
    // 任務(wù) 1
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù)1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    });
    // 任務(wù) 2
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù)2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    });
    // 任務(wù) 3
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù)3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    });
    
    NSLog(@"end");
}

打印結(jié)果:

currentThread---<NSThread: 0x600002314640>{number = 1, name = main}
begin
任務(wù) 1---<NSThread: 0x600002314640>{number = 1, name = main}
任務(wù) 2---<NSThread: 0x600002314640>{number = 1, name = main}
任務(wù) 3---<NSThread: 0x600002314640>{number = 1, name = main}
end

可見(jiàn)洞翩,同步+串行 任務(wù)中

  • 所有的任務(wù)都是在當(dāng)前線程(此例中為主線程)中執(zhí)行的稽犁,未開(kāi)啟新線程。(同步執(zhí)行不具備開(kāi)啟新線程的能力)
  • 任務(wù)完全按照自上至下按順序執(zhí)行同步執(zhí)行需等待當(dāng)前任務(wù)執(zhí)行完畢才能繼續(xù)向下執(zhí)行)

3.2 同步+并發(fā)

// 同步+并發(fā) 任務(wù)
- (void)sync_concurrent{
    
    // 打印當(dāng)前線程
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"begin");
    
    // 并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 任務(wù) 1
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    });
    // 任務(wù) 2
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    });
    // 任務(wù) 3
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    });
    
    NSLog(@"end");
}

打印結(jié)果:

currentThread---<NSThread: 0x600002aa0100>{number = 1, name = main}
begin
任務(wù) 1---<NSThread: 0x600002aa0100>{number = 1, name = main}
任務(wù) 2---<NSThread: 0x600002aa0100>{number = 1, name = main}
任務(wù) 3---<NSThread: 0x600002aa0100>{number = 1, name = main}
end

同步+并發(fā)從結(jié)果來(lái)看與上面的同步+串行是一樣的骚亿。

  • 所有任務(wù)都是在當(dāng)前線程(此例中為主線程)執(zhí)行已亥,未開(kāi)啟新線程(同步執(zhí)行不具備開(kāi)啟新線程的能力)。
  • 任務(wù)自上至下順序執(zhí)行来屠。(同步執(zhí)行需等待當(dāng)前任務(wù)執(zhí)行完畢才能繼續(xù)向下執(zhí)行)虑椎。

前面在說(shuō)并發(fā)隊(duì)列的時(shí)候震鹉,我們說(shuō) 并發(fā)隊(duì)列 可以讓多個(gè)任務(wù)并發(fā)執(zhí)行,那這里為什么任務(wù)還是按照順序執(zhí)行的呢捆姜?
任務(wù)的執(zhí)行者是線程而非隊(duì)列传趾,雖然并發(fā)隊(duì)列支持多任務(wù)同時(shí)執(zhí)行,但同步并不具備開(kāi)啟線程的能力泥技,只能利用當(dāng)前線程浆兰,當(dāng)前線程只有一個(gè)(主線程),所以任務(wù)還是依次在主線程中執(zhí)行珊豹。

3.3 異步+串行

// 異步+串行 任務(wù)
- (void)async_serial{
    
    // 打印當(dāng)前線程
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"begin");
    
    // 串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
    
    // 任務(wù) 1
    dispatch_async(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 1---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    });
    // 任務(wù) 2
    dispatch_async(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 2---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    });
    // 任務(wù) 3
    dispatch_async(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 3---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
    });
    
    NSLog(@"end");
}

打印結(jié)果:

currentThread---<NSThread: 0x600000470340>{number = 1, name = main}
begin
end
任務(wù) 1---<NSThread: 0x60000043dc00>{number = 6, name = (null)}
任務(wù) 2---<NSThread: 0x60000043dc00>{number = 6, name = (null)}
任務(wù) 3---<NSThread: 0x60000043dc00>{number = 6, name = (null)}

可見(jiàn)簸呈,異步+串行 任務(wù)中

  • 開(kāi)啟了新線程(異步執(zhí)行具有開(kāi)啟線程的能力)但是不管任務(wù)有多少個(gè),只開(kāi)啟一條新線程(串行隊(duì)列的任務(wù)都在同一條線程執(zhí)行)店茶。
  • 所有的任務(wù)都是在beginend之后執(zhí)行的(異步執(zhí)行不需等待任務(wù)完畢蜕便,就可繼續(xù)向下執(zhí)行)。
  • 任務(wù)是按隊(duì)列中的順序執(zhí)行的(串行隊(duì)列每次只有一個(gè)任務(wù)被執(zhí)行忽妒,任務(wù)一個(gè)接一個(gè)執(zhí)行)玩裙。

3.4 異步+并發(fā)

// 異步+并發(fā) 任務(wù)
- (void)async_concurrent{
    
    // 打印當(dāng)前線程
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"begin");
    
    // 并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 任務(wù) 1
    dispatch_async(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 1---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
    // 任務(wù) 2
    dispatch_async(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 2---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
    // 任務(wù) 3
    dispatch_async(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 3---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
    
    NSLog(@"end");
}

打印結(jié)果:

currentThread---<NSThread: 0x600003e7c180>{number = 1, name = main}
begin
end
任務(wù) 1---<NSThread: 0x600003e35580>{number = 3, name = (null)}
任務(wù) 3---<NSThread: 0x600003e353c0>{number = 6, name = (null)}
任務(wù) 2---<NSThread: 0x600003e34bc0>{number = 7, name = (null)}

可見(jiàn),異步+并發(fā) 任務(wù)中

  • 本例啟動(dòng)了三個(gè)線程段直,任務(wù)是無(wú)序的,交替執(zhí)行(異步執(zhí)行具備開(kāi)啟新線程的能力溶诞,并發(fā)隊(duì)列可利用多個(gè)線程鸯檬,同時(shí)執(zhí)行多個(gè)任務(wù))。
  • 任務(wù)是在beginend之后開(kāi)始執(zhí)行的(異步執(zhí)行不需等待任務(wù)完畢螺垢,就可繼續(xù)向下執(zhí)行)喧务。

3.5 同步+主隊(duì)列

// 同步+主隊(duì)列 任務(wù)
- (void)sync_main{
    
    // 打印當(dāng)前線程
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"begin");
    
    // 主隊(duì)列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 任務(wù) 1
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 1---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
    // 任務(wù) 2
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 2---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
    // 任務(wù) 3
    dispatch_sync(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 3---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
    
    NSLog(@"end");
}

打印結(jié)果:

currentThread---<NSThread: 0x6000021a49c0>{number = 1, name = main}
begin
(lldb) 

同步+主隊(duì)列 任務(wù)中,當(dāng)運(yùn)行至begin之后枉圃,程序崩潰退出了功茴。這是因?yàn)椋?/p>

當(dāng)前線程為主線程,在主線程中執(zhí)行 sync_main 孽亲,相當(dāng)于將sync_main加入主隊(duì)列中坎穿,當(dāng)追加任務(wù)1的時(shí)候,再將任務(wù)1加入主隊(duì)列返劲,由于主隊(duì)列是串行隊(duì)列玲昧,任務(wù)1 就需等待sync_main執(zhí)行完畢,而又因是同步執(zhí)行篮绿,sync_main需等待任務(wù)1執(zhí)行完畢 孵延,這樣的互相等待,就導(dǎo)致了死鎖的發(fā)生亲配。

所以尘应,在主線程惶凝,執(zhí)行“同步+主隊(duì)列”任務(wù)時(shí),

  • 會(huì)導(dǎo)致死鎖的發(fā)生犬钢。

事實(shí)上梨睁,只要是在當(dāng)前串行隊(duì)列中的同步任務(wù)都會(huì)導(dǎo)致死鎖(見(jiàn)4.GCD 的函數(shù)與隊(duì)列復(fù)雜組合示例 中的示例3、4)

但如果將“同步+主隊(duì)列”任務(wù)放到其他線程(非主線程)娜饵,那么并不會(huì)發(fā)生死鎖坡贺,

  • 所有的任務(wù)都將在主線程(而非當(dāng)前線程)執(zhí)行,且任務(wù)按序執(zhí)行箱舞。

3.6 異步+主隊(duì)列

// 異步+主隊(duì)列 任務(wù)
- (void)async_main{
    
    // 打印當(dāng)前線程
    NSLog(@"currentThread---%@",[NSThread currentThread]);
    NSLog(@"begin");
    
    // 主隊(duì)列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 任務(wù) 1
    dispatch_async(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 1---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
    // 任務(wù) 2
    dispatch_async(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 2---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
    // 任務(wù) 3
    dispatch_async(queue, ^{
        sleep(1);                                       // 模擬耗時(shí)操作
        NSLog(@"任務(wù) 3---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
    
    NSLog(@"end");
}

打印結(jié)果:

currentThread---<NSThread: 0x6000012641c0>{number = 1, name = main}
begin
end
任務(wù) 1---<NSThread: 0x6000012641c0>{number = 1, name = main}
任務(wù) 2---<NSThread: 0x6000012641c0>{number = 1, name = main}
任務(wù) 3---<NSThread: 0x6000012641c0>{number = 1, name = main}

異步+主隊(duì)列任務(wù)中遍坟。

  • 所有的任務(wù)都是在主線程中執(zhí)行的(雖然異步執(zhí)行具備開(kāi)啟線程的能力,但因?yàn)槭侵麝?duì)列晴股,所以所有的任務(wù)都在主線程中)愿伴。
  • 任務(wù)均在begin和end之后執(zhí)行(異步執(zhí)行不需等待任務(wù)完畢,就可繼續(xù)向下執(zhí)行)电湘。
  • 任務(wù)是按順序執(zhí)行(主隊(duì)列是串行隊(duì)列隔节,每次只執(zhí)行一個(gè)任務(wù),任務(wù)一個(gè)接一個(gè)執(zhí)行)寂呛。

小結(jié)

首先需要明確的是怎诫,任務(wù)是在『線程』上執(zhí)行的。線程是由系統(tǒng)創(chuàng)建贷痪,不管是隊(duì)列還是函數(shù)都沒(méi)有創(chuàng)建線程的能力幻妓,只能啟用。

『隊(duì)列』只是存放任務(wù)的劫拢。不管串行隊(duì)列還是并發(fā)隊(duì)列肉津,任務(wù)都是先進(jìn)先出的,這是隊(duì)列的基本特性舱沧。只不過(guò)妹沙,串行隊(duì)列中的任務(wù)一次只出一個(gè),須前一個(gè)任務(wù)調(diào)度完畢后才能出下一個(gè)任務(wù)熟吏;而并發(fā)隊(duì)列中的任務(wù)根據(jù)當(dāng)前可用線程數(shù)出隊(duì)列距糖,如果當(dāng)前可用線程數(shù)為3,就出3個(gè)任務(wù)分俯,如果可用線程數(shù)為1肾筐,那么也就如同串行隊(duì)列一樣只出一個(gè)任務(wù)。

『函數(shù)』決定任務(wù)的執(zhí)行方式缸剪。

只要是同步執(zhí)行吗铐,當(dāng)前線程就需等待任務(wù)執(zhí)行完畢后才能繼續(xù)后續(xù)行為;
只要是異步執(zhí)行杏节,當(dāng)前線程調(diào)用了任務(wù)之后唬渗,就可以繼續(xù)后續(xù)行為典阵。

無(wú)論『隊(duì)列』與『函數(shù)』如何搭配,都不會(huì)影響隊(duì)列和函數(shù)的基本特性镊逝。

對(duì)于同步函數(shù)壮啊,因?yàn)橐却蝿?wù)的返回結(jié)果,所以也就沒(méi)有必要啟動(dòng)線程撑蒜。
對(duì)于異步函數(shù)歹啼,因?yàn)楫?dāng)前線程此刻并不關(guān)心任務(wù)的結(jié)果,需要立即往后執(zhí)行座菠,所以任務(wù)自然也就不能放在當(dāng)前線程狸眼,不然還是阻塞了當(dāng)前線程,這與異步本就是相悖的浴滴。

所以:

  • ① 只要是同步執(zhí)行的任務(wù)拓萌,就不會(huì)開(kāi)啟新的線程,要么在當(dāng)前線程執(zhí)行升略,要么在主線程執(zhí)行(主隊(duì)列)微王。
  • ② 只要是異步執(zhí)行的任務(wù),任務(wù)就不可能在調(diào)度任務(wù)的線程執(zhí)行(主隊(duì)列除外)品嚣,要么開(kāi)啟線程炕倘,要么在主線程執(zhí)行(主隊(duì)列)。
  • 主隊(duì)列很特殊腰根,只要追加到主隊(duì)列中的任務(wù)就一定要主線程執(zhí)行激才,不管主線程有多忙,它會(huì)一直等待主線程空閑下來(lái)再執(zhí)行额嘿。并且在主線程同步執(zhí)行主隊(duì)列任務(wù)時(shí)會(huì)發(fā)生死鎖(事實(shí)上,在當(dāng)前串行隊(duì)列中劣挫,同步追加任務(wù)都是死鎖册养,參看4.GCD 的函數(shù)與隊(duì)列復(fù)雜組合示例 中的示例3、4)压固。
  • ④ 只有異步并發(fā)的任務(wù)球拦,才無(wú)序執(zhí)行。

4. GCD 的函數(shù)與隊(duì)列的組合示例

理解了 函數(shù)隊(duì)列 的特性之后帐我,我們來(lái)看一下他們組合使用的情況坎炼。

示例1:異步并發(fā)任務(wù)的執(zhí)行,下面代碼輸出的結(jié)果是什么拦键?

- (void)textDemo1{
    
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

程序的代碼是自上而下依次執(zhí)行的谣光,當(dāng)前線程為主線程,首先創(chuàng)建了一個(gè)并發(fā)隊(duì)列芬为,接著主線程輸出“1”萄金;追加異步任務(wù)1并發(fā)隊(duì)列蟀悦,因?yàn)?code>異步的特性,所以主線程會(huì)接著向下執(zhí)行氧敢,輸出“5”日戈;
并發(fā)隊(duì)列中的異步任務(wù)會(huì)開(kāi)啟子線程,所以接下來(lái)在子線程1中輸出“2”孙乖;繼而再次追加異步任務(wù)2并發(fā)隊(duì)列浙炼,同樣由于異步的特性,當(dāng)前子線程1不做等待直接向下執(zhí)行唯袄,輸出“4”弯屈,最后執(zhí)行異步任務(wù)2,開(kāi)啟了另一個(gè)子線程2越妈,輸出“3”季俩。

所以結(jié)果為 1 5 2 4 3 。

需要注意的是:并不是只要是這種嵌套形式就一定是這樣的順序執(zhí)行的梅掠,前提是各任務(wù)的耗時(shí)是相似的酌住。如果在此例中NSLog(@"5")之前添加代碼sleep(1)讓主線程休眠1秒鐘模擬這一部分的任務(wù)是更復(fù)雜的,那么結(jié)果將會(huì)是 1 2 4 3 5阎抒。之前我們提到酪我,任務(wù)的執(zhí)行完畢時(shí)間取決于任務(wù)復(fù)雜度CPU的調(diào)度

示例2:同步任務(wù)在并發(fā)隊(duì)列中的執(zhí)行且叁,下面代碼輸出的結(jié)果是什么都哭?

- (void)textDemo2{
    
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

創(chuàng)建并發(fā)隊(duì)列,首先輸出“1”逞带,繼而追加異步任務(wù)并發(fā)隊(duì)列欺矫,由于異步的特性,所以主線程繼續(xù)執(zhí)行展氓,輸出“5”穆趴,然后在異步任務(wù)開(kāi)啟的子線程1中,輸出“2”遇汞,繼而追加同步任務(wù)并發(fā)隊(duì)列未妹,因?yàn)槭?code>同步任務(wù),所以當(dāng)前線程1需等待同步任務(wù)的執(zhí)行完畢空入,也就是需等待輸出“3”執(zhí)行完畢之后络它,才能繼續(xù)輸出“4”,所以答案為:1 5 2 3 4歪赢。

示例3:當(dāng)前串行隊(duì)列中的同步任務(wù)執(zhí)行化戳,下面代碼輸出的結(jié)果是什么?

- (void)textDemo3{
    
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

此例中創(chuàng)建的隊(duì)列為串行隊(duì)列轨淌,首先輸出“1”迂烁,追加異步任務(wù)1串行隊(duì)列中看尼,由于是異步任務(wù),所以盟步,主線程直接向下執(zhí)行藏斩,輸出“5”,
然后調(diào)度串行隊(duì)列中的異步任務(wù)1却盘,在異步任務(wù)塊內(nèi)狰域,代碼依然是順序執(zhí)行的,我們將任務(wù)細(xì)分來(lái)看黄橘,也就是串行隊(duì)列中任務(wù)如下圖布局兆览,

根據(jù)FIFO原則,先調(diào)度NSLog(@"2")輸出”2“塞关,繼而追加同步任務(wù)串行隊(duì)列抬探,將NSLog(@"3")任務(wù)入隊(duì),此時(shí)串行隊(duì)列中任務(wù)如下圖布局

因?yàn)樽詈笞芳拥氖?code>同步任務(wù)帆赢,所以忍饰,sync要等待NSLog(@"3")執(zhí)行完畢才能繼續(xù)向下執(zhí)行叉抡,而又由于是串行隊(duì)列汁胆,需保證任務(wù)按順序執(zhí)行贮乳,NSLog(@"3")需等待NSLog(@"4"),NSLog(@"4")需等待sync瘾婿。

這樣互相等待蜻牢,就導(dǎo)致了死鎖的發(fā)生,所以最終結(jié)果為 1 5 2 死鎖偏陪。

在開(kāi)啟的子線程1中輸出“2”抢呆,接下來(lái)追加同步任務(wù)1串行隊(duì)列,此時(shí)串行隊(duì)列中有兩個(gè)任務(wù)(還未執(zhí)行完畢的異步任務(wù)1和追加的同步任務(wù)1)笛谦。因?yàn)槭荖SLog(@"3")任務(wù)是同步執(zhí)行的镀娶,所以隊(duì)列內(nèi)的異步任務(wù)需要等待NSLog(@"3")執(zhí)行完畢才能執(zhí)行完,又因?yàn)槭谴嘘?duì)列揪罕,所以NSLog(@"3")需要等待異步任務(wù)執(zhí)行完畢。這樣互相等待宝泵,就導(dǎo)致了死鎖的發(fā)生好啰。

示例4:那如果將NSLog(@"4");任務(wù)去掉呢?

- (void)textDemo4{
    
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
    });
    NSLog(@"5");
}

前面的分析過(guò)程同示例3是一樣的:創(chuàng)建串行隊(duì)列儿奶,主線程輸出”1“框往,追加異步任務(wù)1串行隊(duì)列,主線程輸出”5“闯捎。
然后調(diào)度串行隊(duì)列中的異步任務(wù)椰弊,輸出”2“许溅,繼續(xù)追加同步任務(wù)1串行隊(duì)列,此時(shí)隊(duì)列中有兩個(gè)任務(wù)(未執(zhí)行完畢的異步任務(wù)1和剛追加的同步任務(wù)1
雖然異步任務(wù)中沒(méi)有NSLog(@"4")任務(wù)秉版,但是整個(gè)異步任務(wù)依然需要等待 同步任務(wù)執(zhí)行完畢贤重,而由于串行隊(duì)列,同步任務(wù)需要等待異步任務(wù)執(zhí)行完畢清焕,這樣的互相等待還是會(huì)導(dǎo)致死鎖并蝗。

示例5:下面看一個(gè)選擇題示例

- (void) textDemo5{//
    dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    // 1 2 3
    //  0 (7 8 9)
    dispatch_async(queue, ^{ 
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    
    NSLog(@"0");

    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });
    // A: 1230789
    // B: 1237890
    // C: 3120798
    // D: 2137890
}

示例中為并發(fā)隊(duì)列,異步并發(fā)的情況秸妥,在同等水平的任務(wù)下是沒(méi)有固定順序的滚停,輸出”3“是同步任務(wù),也就是說(shuō)從輸出”0“開(kāi)始必須等待輸出”3“執(zhí)行完畢粥惧,所以”3“一定在”0“之前键畴;
輸出”0“之后的任務(wù)都是異步并發(fā)的,所以”7“突雪、”8“起惕、”9“一定在”0“之后。
滿(mǎn)足條件的為A和C挂签。

5.GCD線程間的通訊

在開(kāi)發(fā)中疤祭,我們通常將耗時(shí)的任務(wù)放置到子線程,主線程繼續(xù)處理其他任務(wù)饵婆,待子線程任務(wù)完成后勺馆,再通知主線程進(jìn)行刷新UI等操作,那么如何在子線程中通知主線程執(zhí)行任務(wù)侨核?

其實(shí)很簡(jiǎn)單草穆,就是借助主隊(duì)列,因主隊(duì)列中的任務(wù)一定是在主線程中執(zhí)行的搓译。

/**
 * 線程間通信
 */
- (void)communication {
    
    NSLog(@"begin");
    // 獲取全局并發(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, ^{
        // 子線程執(zhí)行
        // 1. 獲取圖片 imageUrl
        NSURL *imageUrl = [NSURL URLWithString:@"https://xxxxx.jpg"];
        // 2. 從 imageUrl 中讀取數(shù)據(jù)(下載圖片) -- 耗時(shí)操作
        NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
        // 3. 通過(guò)二進(jìn)制 data 創(chuàng)建 image
        UIImage *image = [UIImage imageWithData:imageData];
        
        NSLog(@"1---%@",[NSThread currentThread]);
        
        // 回到主線程
        dispatch_async(mainQueue, ^{
            self.imageView.image = image;
            NSLog(@"2---%@",[NSThread currentThread]);
        });
    });
    NSLog(@"end");
}

上述代碼在子線程中請(qǐng)求圖片數(shù)據(jù)悲柱,數(shù)據(jù)獲取完畢后,回到主線程更新圖片些己。打印的結(jié)果如下豌鸡,是符合我們預(yù)期的。

begin
end
1---<NSThread: 0x600003540980>{number = 3, name = (null)}
2---<NSThread: 0x60000350c240>{number = 1, name = main}

拓展:為什么一定要在主線程更新UI呢段标?

首先涯冠,UIKit不是線程安全的,當(dāng)多個(gè)線程同時(shí)操作UI時(shí)逼庞,搶奪資源蛇更,有可能導(dǎo)致崩潰,UI異常等問(wèn)題。假如在兩個(gè)線程中設(shè)置了同一張背景圖片派任,很有可能就會(huì)由于背景圖片被釋放兩次砸逊,使得程序崩潰≌乒洌或者某一個(gè)線程中遍歷找尋某個(gè)subView师逸,然而在另一個(gè)線程中刪除了該subView,那么就會(huì)造成錯(cuò)亂颤诀。

那么為什么不將UIKit設(shè)計(jì)成線程安全的呢字旭?為了性能效率
多線程訪問(wèn)必定會(huì)涉及線程同步的開(kāi)銷(xiāo)問(wèn)題崖叫,UIKit是一個(gè)龐大的框架遗淳,UI操作涉及到渲染訪問(wèn)各種View對(duì)象的屬性,如果為了確保UI操作的線程安全心傀,一來(lái)會(huì)帶來(lái)巨大的成本(視圖層級(jí)深屈暗,屬性多),二來(lái)會(huì)耗費(fèi)大量的資源拖慢運(yùn)行速度(加鎖)脂男。這未必會(huì)帶來(lái)更高的效率养叛。

所以,UI的操作最好在單一線程里執(zhí)行宰翅,那放到哪個(gè)線程呢弃甥?
在Cocoa Touch框架中,UIApplication初始化工作是在主線程進(jìn)行的汁讼,而界面上所有的視圖都是在UIApplication實(shí)例的葉子節(jié)點(diǎn)上(內(nèi)存管理角度)淆攻,所以所有的用戶(hù)交互事件都是在主線程上進(jìn)行傳遞,在主線程響應(yīng)嘿架。

主線程操作UI瓶珊,能夠幫助我們避免一些不必要的麻煩和缺陷,也就成了一個(gè)約定俗成的開(kāi)發(fā)規(guī)則耸彪。

那么伞芹,子線程到底能不能更新UI呢?

有時(shí)也可以蝉娜,但是會(huì)有問(wèn)題唱较。在子線程能更新的UI是一個(gè)假象,其實(shí)是子線程代碼執(zhí)行完畢了召川,又自動(dòng)進(jìn)入到了主線程绊汹,執(zhí)行了子線程中的UI更新的函數(shù)棧,這中間的時(shí)間非常的短扮宠,就讓大家誤以為分線程可以更新UI。如果子線程一直在運(yùn)行,則子線程中的UI更新的函數(shù)棧坛增,主線程就無(wú)法獲知获雕,那就無(wú)法更新直到子線程結(jié)束。

6. GCD常用函數(shù)

在使用GCD時(shí)收捣,我們通常使用”異步+并發(fā)“的形式届案,在子線程執(zhí)行相關(guān)的任務(wù),這可以讓主線程繼續(xù)執(zhí)行后續(xù)操作罢艾,也可以充分利用CPU內(nèi)核更快地執(zhí)行多個(gè)任務(wù)楣颠。但是異步并發(fā)的任務(wù)是無(wú)序執(zhí)行的,并且哪一個(gè)任務(wù)先執(zhí)行完畢也是不確定的咐蚯。如果隊(duì)列中某一個(gè)(組)任務(wù)的執(zhí)行是依賴(lài)于另一個(gè)(組)任務(wù)的數(shù)據(jù)童漩,該如何處理呢?

GCD提供的柵欄函數(shù)春锋、調(diào)度組矫膨、信號(hào)量可以解決這一問(wèn)題。

6.1柵欄函數(shù):dispatch_barrier_async/sync

柵欄函數(shù)相當(dāng)于在隊(duì)列中的特定位置設(shè)立了一個(gè)”柵欄“期奔,并且這個(gè)柵欄中也可以執(zhí)行任務(wù)

只有當(dāng)柵欄前面的任務(wù)全部調(diào)度完畢后才可以繼續(xù)調(diào)度柵欄任務(wù)以及柵欄后面的任務(wù)侧馅。

/**
 * 柵欄函數(shù) dispatch_barrier_async
 */
- (void)barrier {
    
    dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"begin");
    dispatch_async(queue, ^{
        // 追加任務(wù) 1
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"1      ---%@",[NSThread currentThread]);// 打印當(dāng)前線程
    });
    dispatch_async(queue, ^{
        // 追加任務(wù) 2
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"2      ---%@",[NSThread currentThread]);// 打印當(dāng)前線程
    });
    
    dispatch_barrier_async(queue, ^{
        // 追加任務(wù) barrier
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當(dāng)前線程
    });
    
    dispatch_async(queue, ^{
        // 追加任務(wù) 3
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"3      ---%@",[NSThread currentThread]);// 打印當(dāng)前線程
    });
    dispatch_async(queue, ^{
        // 追加任務(wù) 4
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"4      ---%@",[NSThread currentThread]);// 打印當(dāng)前線程
    });
    NSLog(@"end");
}

打印結(jié)果:

begin
end
2      ---<NSThread: 0x600003cefbc0>{number = 5, name = (null)}
1      ---<NSThread: 0x600003ceff80>{number = 4, name = (null)}
barrier---<NSThread: 0x600003ceff80>{number = 4, name = (null)}
3      ---<NSThread: 0x600003ceff80>{number = 4, name = (null)}
4      ---<NSThread: 0x600003cefbc0>{number = 5, name = (null)}

如果將上例中的dispatch_barrier_async函數(shù)換成dispatch_barrier_sync,那么打印結(jié)果如下:

begin
2      ---<NSThread: 0x60000186a740>{number = 6, name = (null)}
1      ---<NSThread: 0x600001868140>{number = 5, name = (null)}
barrier---<NSThread: 0x600001820680>{number = 1, name = main}
end
3      ---<NSThread: 0x600001868140>{number = 5, name = (null)}
4      ---<NSThread: 0x600001818bc0>{number = 8, name = (null)}

需要注意:在使用柵欄函數(shù)的時(shí)候呐萌,使用自定義并發(fā)隊(duì)列才有意義馁痴。如果是全局并發(fā)隊(duì)列,這個(gè)柵欄函數(shù)不起作用肺孤;如果用的是串行隊(duì)列罗晕,因其本身就是按序執(zhí)行。所以沒(méi)有意義渠旁。

6.2 調(diào)度組:dispatch_group

在追加到隊(duì)列中的多個(gè)任務(wù)全部執(zhí)行完畢后攀例,再執(zhí)行接下來(lái)的處理操作,這種需求經(jīng)常會(huì)出現(xiàn)在我們的程序中顾腊。
如果只使用一個(gè)串行隊(duì)列時(shí)粤铭,只要將想要執(zhí)行的任務(wù)全部追加到隊(duì)列中,并在最后執(zhí)行結(jié)束的操作即可杂靶。
如果是使用并發(fā)隊(duì)列呢梆惯?或者使用多個(gè)不同隊(duì)列,想要實(shí)現(xiàn)這種需求就變得相當(dāng)復(fù)雜吗垮。

串行隊(duì)列雖然可以實(shí)現(xiàn)這個(gè)需求垛吗,但耗時(shí)任務(wù)依然需要一個(gè)接一個(gè)執(zhí)行,執(zhí)行效率不高烁登,更多的情況是怯屉,我們希望任務(wù)在異步的子線程去執(zhí)行,而不影響用戶(hù)交互與主線程事務(wù)。所以我們需要使用并發(fā)隊(duì)列來(lái)完成此類(lèi)需求锨络,但是異步并發(fā)的任務(wù)是在多個(gè)子線程中被調(diào)度的赌躺,且哪一個(gè)先執(zhí)行完畢并不是確定的,這就給此類(lèi)需求帶來(lái)了極大的難度羡儿,對(duì)于不同隊(duì)列中的任務(wù)的監(jiān)控也是如此礼患。所以GCD提供了調(diào)度組dispatch_group

dispatch_group中常用的函數(shù)如下:

  • dispatch_group_notify
  • dispatch_group_enter/dispatch_group_enter
  • dispatch_group_wait

調(diào)度組保存了與其相關(guān)聯(lián)的待執(zhí)行的任務(wù)的數(shù)量掠归,當(dāng)一個(gè)新的任務(wù)被關(guān)聯(lián)的時(shí)候增加它的計(jì)數(shù)(+1)缅叠,當(dāng)一個(gè)任務(wù)執(zhí)行完畢的時(shí)候減少它的計(jì)數(shù)(-1)。當(dāng)所有與調(diào)度組相關(guān)聯(lián)的任務(wù)全部完成的時(shí)候虏冻,調(diào)度組會(huì)通過(guò)dispatch_group_waitdispatch_group_notify來(lái)通知整個(gè)組的任務(wù)完成肤粱。

  • dispatch_group_notify:監(jiān)聽(tīng)組內(nèi)任務(wù)的執(zhí)行完畢

在使用時(shí),我們通過(guò)dispatch_group_async函數(shù)將任務(wù)追加到指定隊(duì)列中并且關(guān)聯(lián)到調(diào)度組兄旬,這些隊(duì)列可以不相關(guān)狼犯,也就是可以將任務(wù)提交到不同的隊(duì)列中,只要關(guān)聯(lián)的是同一個(gè)調(diào)度組领铐。調(diào)度組會(huì)監(jiān)聽(tīng)組內(nèi)任務(wù)的執(zhí)行情況悯森,當(dāng)所有與調(diào)度組相關(guān)聯(lián)的任務(wù)全部完成的時(shí)候,就會(huì)回調(diào)dispatch_group_notify函數(shù)绪撵,執(zhí)行指定任務(wù)瓢姻。

下面我們實(shí)現(xiàn)這樣一個(gè)案例:有4個(gè)任務(wù),任務(wù)1音诈、任務(wù)2幻碱、任務(wù)3、任務(wù)4细溅;
任務(wù)3必須在任務(wù)2之后褥傍,任務(wù)4必須在前3個(gè)任務(wù)執(zhí)行完畢后,才能執(zhí)行喇聊,并且任務(wù)4需要主線程執(zhí)行恍风。

分析如下:

任務(wù)3必須在任務(wù)2之后,所以讓這兩個(gè)任務(wù)串行執(zhí)行誓篱,同時(shí)朋贬,任務(wù)2和任務(wù)3整體可以和任務(wù)1并發(fā)執(zhí)行,最后窜骄,任務(wù)4等待前三個(gè)任務(wù)執(zhí)行完畢再執(zhí)行锦募,利用調(diào)度組實(shí)現(xiàn)如下:

案例1:

/**
 * 調(diào)度組 dispatch_group_notify
 */
-(void)groupNotify{
    
    // 獲取全局并發(fā)隊(duì)列
    dispatch_queue_t globalQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 創(chuàng)建串行隊(duì)列
    dispatch_queue_t serialQuene = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
    // 創(chuàng)建調(diào)度組
    dispatch_group_t group = dispatch_group_create();
    
    NSLog(@"begin" );
    
    
    // 將任務(wù)1提交到全局并發(fā)隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_async(group, globalQuene, ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"1     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
    });
    // 將任務(wù)2提交到串行隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_async(group, serialQuene, ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"2     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
    });
    // 將任務(wù)3提交到串行隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_async(group, serialQuene, ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"3     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
    });
    // 將任務(wù)4提交到主隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"4     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
        NSLog(@"group---end");
    });
    
    NSLog(@"end");
}

打印如下:

begin
end
2     ---<NSThread: 0x600000c74600>{number = 6, name = (null)}
1     ---<NSThread: 0x600000c79cc0>{number = 7, name = (null)}
3     ---<NSThread: 0x600000c79cc0>{number = 4, name = (null)}
4     ---<NSThread: 0x6000013445c0>{number = 1, name = main}
group---end

示例的結(jié)果也可能1234。但是dispatch_group_notify的任務(wù)執(zhí)行一定是在三個(gè)異步任務(wù)執(zhí)行完畢之后才進(jìn)行調(diào)用的邻遏,這和任務(wù)所屬隊(duì)列無(wú)關(guān)糠亩,也和dispatch_group_notify所在的位置無(wú)關(guān)虐骑,即使我們將dispatch_group_notify函數(shù)放到三個(gè)異步任務(wù)的前面,結(jié)果也是一樣的削解。

dispatch_group_notify函數(shù)會(huì)監(jiān)聽(tīng)與調(diào)度組相關(guān)聯(lián)的所有的任務(wù)執(zhí)行完畢后才會(huì)執(zhí)行自身任務(wù)富弦。底層是通過(guò)信號(hào)量實(shí)現(xiàn)的。

  • dispatch_group_enter / dispatch_group_leave

在上例中氛驮,我們通過(guò)dispatch_group_async函數(shù)將任務(wù)提交到隊(duì)列并且關(guān)聯(lián)調(diào)度組。實(shí)際上济似,還有一種更加靈活的方式矫废,就是dispatch_group_enterdispatch_group_leave的搭配使用。

// 方式一:
dispatch_group_async(group, queue, ^{
  // 砰蠢。蓖扑。。
}); 
 
// 方式二:
dispatch_group_enter(group);
dispatch_async(queue, ^{
  //台舱。律杠。。
  dispatch_group_leave(group);
});

從一定程度上竞惋,方式一和方式二是等價(jià)的柜去。(dispatch_group_async底層也是通過(guò)dispatch_group_enterdispatch_group_leave實(shí)現(xiàn)的。)

dispatch_group_enter: 標(biāo)記為入組拆宛,執(zhí)行一次嗓奢,相當(dāng)于組內(nèi)待執(zhí)行的任務(wù)數(shù)+1;
dispatch_group_leave: 標(biāo)記為出組浑厚,執(zhí)行一次股耽,相當(dāng)于組內(nèi)待執(zhí)行的任務(wù)數(shù)-1;
當(dāng)組內(nèi)待執(zhí)行的任務(wù)數(shù)為0時(shí)钳幅,會(huì)使dispatch_group_wait解除阻塞物蝙,并回調(diào)dispatch_group_notify函數(shù)。

我們將上面的案例換成dispatch_group_enter / dispatch_group_leave方式如下:

案例2:

/**
 * 調(diào)度組 dispatch_group_enter敢艰、dispatch_group_leave
 */
-(void)groupEnter_leave{
    
    // 獲取全局并發(fā)隊(duì)列
    dispatch_queue_t globalQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 創(chuàng)建串行隊(duì)列
    dispatch_queue_t serialQuene = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
    // 創(chuàng)建調(diào)度組
    dispatch_group_t group = dispatch_group_create();
    
    NSLog(@"begin" );
    
    // 將任務(wù)1提交到全局并發(fā)隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_enter(group);
    dispatch_async(globalQuene, ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"1     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
        dispatch_group_leave(group);
    });
    
    // 將任務(wù)2提交到串行隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_enter(group);
    dispatch_async(serialQuene, ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"2     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
        dispatch_group_leave(group);
    });
    // 將任務(wù)3提交到串行隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_enter(group);
    dispatch_async(serialQuene, ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"3     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
        dispatch_group_leave(group);
    });
    
    // 將任務(wù)4提交到主隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"4     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
        NSLog(@"group---end");
    });
    
    NSLog(@"end");
    
}

執(zhí)行結(jié)果同上例是一致的诬乞。

在使用此方式實(shí)現(xiàn)的時(shí)候,一定要注意保證dispatch_group_enterdispatch_group_leave成對(duì)出現(xiàn)盖矫,不然dispatch_group_notify可能無(wú)法回調(diào)丽惭。

  • dispatch_group_wait

一旦調(diào)用dispatch_group_wait函數(shù),該函數(shù)就會(huì)處理調(diào)用的狀態(tài)而不返回值辈双,只有當(dāng)

  • 函數(shù)的currentThread停止
  • 或到達(dá)wait函數(shù)指定的等待的時(shí)間
  • 或Dispatch Group中的操作全部執(zhí)行完畢

該函數(shù)才會(huì)返回值责掏。也就是說(shuō),dispatch_group_wait會(huì)一直等待湃望,它會(huì)阻塞當(dāng)前線程换衬,直到上述條件的發(fā)生痰驱。

當(dāng)指定timeout為DISPATCH_TIME_FOREVER時(shí)就意味著永久等待;
當(dāng)指定timeout為DISPATCH_TIME_NOW時(shí)就意味不用任何等待即可判定關(guān)聯(lián)于Dispatch Group的任務(wù)是否全部執(zhí)行結(jié)束瞳浦。

如果函數(shù)的返回值為0担映,意味著與調(diào)度組相關(guān)聯(lián)的任務(wù)全部執(zhí)行完畢。
如果函數(shù)的返回值非0叫潦,意味著在指定時(shí)間內(nèi)蝇完,與調(diào)度組相關(guān)聯(lián)的任務(wù)并沒(méi)有全部執(zhí)行完畢。
你可以對(duì)返回值進(jìn)行條件判斷以確定是否超出等待周期矗蕊。

將案例1的代碼加上dispatch_group_wait函數(shù)如下:

/**
 * 調(diào)度組 dispatch_group_notify
 */
-(void)groupNotify{
    
    // 獲取全局并發(fā)隊(duì)列
    dispatch_queue_t globalQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 創(chuàng)建串行隊(duì)列
    dispatch_queue_t serialQuene = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_SERIAL);
    // 創(chuàng)建調(diào)度組
    dispatch_group_t group = dispatch_group_create();
    
    NSLog(@"begin" );
    
    
    // 將任務(wù)1提交到全局并發(fā)隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_async(group, globalQuene, ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"1     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
    });
    // 將任務(wù)2提交到串行隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_async(group, serialQuene, ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"2     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
    });
    // 將任務(wù)3提交到串行隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_async(group, serialQuene, ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"3     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
    });
    // 將任務(wù)4提交到主隊(duì)列并關(guān)聯(lián)調(diào)度組
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"4     ---%@",[NSThread currentThread]); // 打印當(dāng)前線程
        NSLog(@"group---end");
    });
    
    long time = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"time = %ld",time);
    
    NSLog(@"end");
    
}

打印結(jié)果如下:

begin
2     ---<NSThread: 0x6000036dcd40>{number = 6, name = (null)}
1     ---<NSThread: 0x6000036d8fc0>{number = 4, name = (null)}
3     ---<NSThread: 0x6000036dcd40>{number = 6, name = (null)}
time = 0
end
4     ---<NSThread: 0x60000369c040>{number = 1, name = main}
group---end

當(dāng)timeout的值設(shè)為DISPATCH_TIME_FOREVER短蜕,意味著一直等待,dispatch_group_wait會(huì)一直阻塞當(dāng)前線程傻咖,直到與調(diào)度組相關(guān)聯(lián)的任務(wù)全部執(zhí)行完畢朋魔,所以當(dāng)任務(wù)2、任務(wù)1卿操、任務(wù)3完成后才繼續(xù)執(zhí)行主線程代碼警检,打印的time值為0,代表關(guān)聯(lián)的任務(wù)全部執(zhí)行完畢害淤。最后回調(diào)dispatch_group_notify函數(shù)扇雕,執(zhí)行任務(wù)4。

如果將time設(shè)為DISPATCH_TIME_NOW筝家,打印的結(jié)果如下:

begin
time = 49
end
1     ---<NSThread: 0x6000038b0ac0>{number = 6, name = (null)}
2     ---<NSThread: 0x6000038c6c00>{number = 3, name = (null)}
3     ---<NSThread: 0x6000038c6c00>{number = 3, name = (null)}
4     ---<NSThread: 0x6000038f0100>{number = 1, name = main}
group---end

6.3 信號(hào)量:dispatch_semaphore

dispatch_semaphore是基于mach內(nèi)核的信號(hào)量接口實(shí)現(xiàn)的洼裤,信號(hào)量很復(fù)雜,因它建立在操作系統(tǒng)的復(fù)雜性之上溪王。不過(guò)我們可以用簡(jiǎn)單的方式去理解它腮鞍,本質(zhì)上,信號(hào)量是一個(gè)持有計(jì)數(shù)的信號(hào)莹菱,信號(hào)可以在線程之間互相傳遞移国,根據(jù)信號(hào)所持有的計(jì)數(shù)決定線程行為。

同時(shí)道伟,信號(hào)量也很強(qiáng)大迹缀。

  • 信號(hào)量可以控制線程并發(fā)訪問(wèn)的最大數(shù)量;
  • 信號(hào)量可以保持線程同步蜜徽,使異步任務(wù)同步化祝懂,讓多個(gè)異步不同隊(duì)列的線程串行執(zhí)行;
  • 信號(hào)量可以保證線程安全拘鞋,給線程加鎖砚蓬;

信號(hào)量可以讓你控制多個(gè)消費(fèi)者對(duì)有限數(shù)量資源的訪問(wèn)。如果你創(chuàng)建了一個(gè)有著兩個(gè)資源的信號(hào)量盆色,那同時(shí)最多只能有兩個(gè)線程可以訪問(wèn)臨界區(qū)灰蛙。其他想使用資源的線程必須在一個(gè)FIFO隊(duì)列里等待祟剔。如果創(chuàng)建了一個(gè)只有一個(gè)資源的信號(hào)量,那么同一時(shí)刻只能有一個(gè)線程可以訪問(wèn)臨界區(qū)摩梧。

信號(hào)量中常用的函數(shù)有:

dispatch_semaphore_create:創(chuàng)建一個(gè)帶有初始值的信號(hào)量
dispatch_semaphore_signal:發(fā)送一個(gè)信號(hào)物延,讓信號(hào)量+ 1
dispatch_semaphore_wait:等待一個(gè)信號(hào),讓信號(hào)量-1仅父,如果信號(hào)量小于0叛薯,根據(jù)設(shè)置的超時(shí)時(shí)間等待(阻塞所在線程)。

信號(hào)量是如何工作的呢笙纤?

我們先看看AFNetworking中信號(hào)量的應(yīng)用

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    // 初始化信號(hào)量
    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"];
        }
        // 發(fā)信號(hào)
        dispatch_semaphore_signal(semaphore);
    }];
    // 檢驗(yàn)信號(hào)
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

首先由dispatch_semaphore_create函數(shù)創(chuàng)建一個(gè)帶有初始值的信號(hào)量案训,也稱(chēng)發(fā)射信號(hào)量。

dispatch_semaphore_t
dispatch_semaphore_create(intptr_t value);

參數(shù)指定信號(hào)量的起始值粪糙。這個(gè)數(shù)字表示最多可以有多少線程訪問(wèn)臨界區(qū)(注:這里初始值為0,也就是說(shuō)忿项,有人想使用信號(hào)量必然會(huì)被阻塞蓉冈,直到有人增加信號(hào)量。)

由于這是一個(gè)異步任務(wù)轩触,所以我們先不管異步子線程中的任務(wù)寞酿,直接主線程繼續(xù)向下執(zhí)行

intptr_t
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)

等待一個(gè)信號(hào),遞減信號(hào)量的計(jì)數(shù)脱柱。如果信號(hào)量小于零伐弹,就會(huì)根據(jù)設(shè)置的超時(shí)時(shí)間等待(阻塞所在線程)。

這和調(diào)度組中dispatch_group_wait很相似榨为,實(shí)際上惨好,dispatch_group_wait就是由信號(hào)量實(shí)現(xiàn)的。

這里初始的信號(hào)量值為0随闺,進(jìn)入dispatch_semaphore_wait函數(shù)日川,信號(hào)量值減1,變?yōu)?1矩乐,小于0龄句,此時(shí)的超時(shí)時(shí)間設(shè)置為DISPATCH_TIME_FOREVER,所以它會(huì)一直等待并阻塞所在線程散罕,直到正確的接收到一個(gè)信號(hào)分歇。

由于主線程被阻塞,此時(shí)我們關(guān)注異步子線程欧漱,子線程處理一定的任務(wù)后职抡,進(jìn)行了dispatch_semaphore_signal函數(shù)的調(diào)用

intptr_t
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

dispatch_semaphore_signal函數(shù)會(huì)發(fā)送信號(hào),使信號(hào)量的計(jì)數(shù)+1硫椰。如果以前的值小于零繁调,則這個(gè)函數(shù)將喚醒當(dāng)前正在dispatch_semaphore_wait這里等待的線程(如果一個(gè)線程被喚醒則返回非零萨蚕,否則返回零。)

最后蹄胰,主線程被喚醒岳遥,異步任務(wù)得以正確返回以供AFNetworking其他模塊使用。

關(guān)于信號(hào)量的底層實(shí)現(xiàn)裕寨,參閱iOS多線程編程(五) GCD的底層原理浩蓉。

6.4 一次執(zhí)行函數(shù):dispatch_once

dispatch_once 函數(shù)保證了代碼在程序運(yùn)行期間只執(zhí)行一次,如果有這樣的需求時(shí)宾袜,就可以使用dispatch_once函數(shù)捻艳。通常,單例的創(chuàng)建就是借助此函數(shù)庆猫。

/**
 * 一次執(zhí)行函數(shù): dispatch_once
 */
- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    <#code to be executed once#>
        // 只執(zhí)行 1 次的代碼(這里面默認(rèn)是線程安全的)
    });
}

6.5 延時(shí)執(zhí)行函數(shù):dispatch_after

如果需要指定任務(wù)在多少秒后執(zhí)行认轨,就可以使用dispatch_after函數(shù)。那么在規(guī)定時(shí)間之后月培,任務(wù)就會(huì)被追加到主隊(duì)列中嘁字,需要注意的是,這個(gè)時(shí)間并不是絕對(duì)精準(zhǔn)的杉畜。

/**
 * 延時(shí)執(zhí)行函數(shù): dispatch_after
 */
- (void)after {

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0 秒后異步追加任務(wù)代碼到主隊(duì)列纪蜒,并開(kāi)始執(zhí)行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
}

6.6 快速迭代函數(shù):dispatch_apply

通常,我們使用for循環(huán)或者for...in函數(shù)進(jìn)行遍歷操作此叠,GCD也提供了快速迭代方法纯续,就是dispatch_apply

使用dispatch_apply函數(shù)會(huì)按照指定的次數(shù)執(zhí)行任務(wù)灭袁。之所以快速猬错,就是利用了多線程”同時(shí)“執(zhí)行的特性。

如果是在串行隊(duì)列中使用dispatch_apply简卧,那么依然要按序一個(gè)個(gè)執(zhí)行兔魂,這體現(xiàn)不出快速迭代的意義。如果在并發(fā)隊(duì)列中進(jìn)行異步執(zhí)行举娩,那么就可以在多個(gè)線程同時(shí)異步遍歷析校。

/**
 * 快速迭代函數(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é)果如下:

apply---begin
0---<NSThread: 0x600003d4c640>{number = 1, name = main}
1---<NSThread: 0x600003d7f000>{number = 5, name = (null)}
2---<NSThread: 0x600003d02880>{number = 6, name = (null)}
3---<NSThread: 0x600003d00940>{number = 4, name = (null)}
5---<NSThread: 0x600003d7f000>{number = 5, name = (null)}
4---<NSThread: 0x600003d4c640>{number = 1, name = main}
apply---end

可見(jiàn),dispatch_apply在并發(fā)隊(duì)列中可以利用不同的線程執(zhí)行任務(wù)铜涉,任務(wù)是無(wú)序的智玻。

但是要注意:
不管是串行隊(duì)列還是并發(fā)隊(duì)列,apply---end都是在最后輸出的芙代,dispatch_apply同步的吊奢,會(huì)等待全部任務(wù)執(zhí)行完畢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纹烹,一起剝皮案震驚了整個(gè)濱河市页滚,隨后出現(xiàn)的幾起案子召边,更是在濱河造成了極大的恐慌,老刑警劉巖裹驰,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隧熙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡幻林,警方通過(guò)查閱死者的電腦和手機(jī)贞盯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)沪饺,“玉大人躏敢,你說(shuō)我怎么就攤上這事≌希” “怎么了件余?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)遭居。 經(jīng)常有香客問(wèn)我蛾扇,道長(zhǎng),這世上最難降的妖魔是什么魏滚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮坟漱,結(jié)果婚禮上鼠次,老公的妹妹穿的比我還像新娘。我一直安慰自己芋齿,他們只是感情好腥寇,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著觅捆,像睡著了一般赦役。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上栅炒,一...
    開(kāi)封第一講書(shū)人閱讀 52,785評(píng)論 1 314
  • 那天掂摔,我揣著相機(jī)與錄音,去河邊找鬼赢赊。 笑死乙漓,一個(gè)胖子當(dāng)著我的面吹牛蚊荣,可吹牛的內(nèi)容都是我干的毯炮。 我是一名探鬼主播私植,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼裸删,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼武福!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起议泵,我...
    開(kāi)封第一講書(shū)人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤驮配,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后同诫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體粤策,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年剩辟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掐场。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贩猎,死狀恐怖熊户,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吭服,我是刑警寧澤嚷堡,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站艇棕,受9級(jí)特大地震影響蝌戒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沼琉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一北苟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧打瘪,春花似錦友鼻、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至僻爽,卻和暖如春虫碉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胸梆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工敦捧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碰镜。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓绞惦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親洋措。 傳聞我的和親對(duì)象是個(gè)殘疾皇子济蝉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

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

  • 1. GCD簡(jiǎn)介 iOS開(kāi)發(fā)中多線程的API主要有pthread,NSThread,NSOperation和GCD...
    安東_Ace閱讀 1,267評(píng)論 0 6
  • 引言 關(guān)于iOS開(kāi)發(fā)中的多線程王滤,一直是工作中的重要組成部分贺嫂。由于難以理解且對(duì)app的用戶(hù)體驗(yàn)影響重大,也是面試中的...
    ZhengLi閱讀 447評(píng)論 4 10
  • 本文的寫(xiě)作目的是為學(xué)習(xí)記錄雁乡,同時(shí)分享給大家第喳,希望大神能夠?qū)ξ闹绣e(cuò)誤的理解進(jìn)行指正。 如果文章內(nèi)容涉及到其他已經(jīng)發(fā)表...
    油菜小白閱讀 684評(píng)論 0 2
  • 一踱稍、簡(jiǎn)介 在iOS所有實(shí)現(xiàn)多線程的方案中曲饱,GCD應(yīng)該是最有魅力的,因?yàn)镚CD本身是蘋(píng)果公司為多核的并行運(yùn)算提出的解...
    Alanxx閱讀 355評(píng)論 0 1
  • 久違的晴天珠月,家長(zhǎng)會(huì)扩淀。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí),離放學(xué)已經(jīng)沒(méi)多少時(shí)間了啤挎。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)驻谆。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,528評(píng)論 16 22