多線程系列篇章計(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ù)都是在
begin
和end
之后執(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ù)是在
begin
和end
之后開(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_wait
或dispatch_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_enter
和dispatch_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_enter
和dispatch_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_enter
和dispatch_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í)行完畢。