什么是GCD與Dispatch Queue谱醇?
GCD:是Grand Central Dispatch縮寫,是Apple公司封裝的一個用于進(jìn)行并發(fā)程序設(shè)計(jì)的一個API窄锅,它以C語言為基礎(chǔ)伶丐,將線程的操作抽象為對隊(duì)列的操作。
??Dispatch Queue:Dispatch Queue是GCD中用到的隊(duì)列類型锐朴,有串行隊(duì)列、并發(fā)隊(duì)列蔼囊、主隊(duì)列等焚志。
GCD有什么用?
在進(jìn)行iOS開發(fā)過程畏鼓,我們經(jīng)常需要對多個任務(wù)進(jìn)行操作酱酬,這時就需要用到線程,iOS開發(fā)中對線程的操作多種多樣云矫,有PThreads膳沽、NSThread、NSOperation以及GCD让禀。
??Pthread:是類Unix系統(tǒng)中用來對線程進(jìn)行操作的一組C語言寫的API挑社,可以在Unix、Mac OX S巡揍、Linux中使用痛阻,學(xué)習(xí)Linux下的C語言多線程會用到這個。
??NSThread:是Apple對Pthread的抽象腮敌,NSThread對Pthread的抽象程度較低阱当,在進(jìn)行多線程開發(fā)時,依然需要進(jìn)行對線程進(jìn)行創(chuàng)建缀皱、使用斗这、銷毀等操作。
??NSOperation Queue:Apple基于GCD封裝的一種基于Queue操作的并發(fā)處理技術(shù)啤斗。
??GCD:基于C語言封裝的一個對任務(wù)進(jìn)行操作的并發(fā)處理技術(shù)表箭。
??在封裝的三種API中,Apple公司最為推崇GCD與NSOperation Queue钮莲,因?yàn)檫@兩種方式讓開發(fā)者的注意力轉(zhuǎn)向如何對并發(fā)程序進(jìn)行設(shè)計(jì)免钻,不用關(guān)注線程到底是怎樣執(zhí)行任務(wù)的,提高了開發(fā)的效率崔拥。關(guān)于并發(fā)程序的設(shè)計(jì)可以查看官方文檔Concurrency and Application Design极舔,里面有對GCD、NSOperation Queue以及其它并發(fā)技術(shù)的詳細(xì)講解链瓦。
需要了解的知識
關(guān)于GCD拆魏,需要串行盯桦、并發(fā)和同步以及異步這些知識。在GCD中渤刃,串行和并發(fā)是用來形容Dispatch Queue的拥峦。
??并發(fā)(concurrent):在操作系統(tǒng)中,同一時間段卖子,有多個程序同時在處理器上執(zhí)行略号,就像這些程序在同時進(jìn)行一樣。在單一CPU的機(jī)器上洋闽,并發(fā)技術(shù)基于時間片技術(shù)玄柠,而在多核機(jī)器上,并發(fā)技術(shù)基于多核技術(shù)的發(fā)展诫舅。在GCD中并發(fā)隊(duì)列中的任務(wù)可以不用等前一個任務(wù)執(zhí)行完就能執(zhí)行羽利。
??串行(serial):既然并發(fā)是多個程序看起來在同時進(jìn)行,那么串行就是多個程序排隊(duì)一個一個執(zhí)行骚勘。在GCD串行隊(duì)列中铐伴,隊(duì)列中的任務(wù)需要等出隊(duì)的任務(wù)執(zhí)行完后才能接著執(zhí)行。
在GCD中俏讹,同步與異步是指任務(wù)之間的制約關(guān)系的。
??同步:同步在操作系統(tǒng)中是指進(jìn)程之間的制約關(guān)系畜吊。一個進(jìn)程要等另一個進(jìn)程執(zhí)行完成后才能執(zhí)行泽疆,而不是同時執(zhí)行,是進(jìn)程之間的執(zhí)行次序的管理玲献。例如殉疼,進(jìn)程B需要用到進(jìn)程A的結(jié)果才能執(zhí)行,因此系統(tǒng)會先執(zhí)行A然后在執(zhí)行B捌年,進(jìn)程A與B的關(guān)系就是同步瓢娜。同理,在GCD中礼预,具有同步關(guān)系的兩個任務(wù)眠砾,一個需要等另一個執(zhí)行完后才能執(zhí)行,某種程度上托酸,同步的效果與串行相似褒颈。
??異步:與同步的意思相反,既然同步是一個接一個執(zhí)行励堡,那么異步就是后一個任務(wù)不必等前一個任務(wù)執(zhí)行完谷丸,即任務(wù)A執(zhí)行的結(jié)果并不影響任務(wù)B,任務(wù)A與任務(wù)B不相關(guān)应结,沒有確定的先后順序刨疼。
Dispatch Queue類型
串行隊(duì)列:任務(wù)按被加入到隊(duì)列中的順序依次執(zhí)行,每次只從隊(duì)中取出一個任務(wù)執(zhí)行。
并發(fā)隊(duì)列:同時執(zhí)行一個或多個任務(wù)揩慕,但任務(wù)仍然是按它們被添加到隊(duì)列中的順序開始的亭畜。
主隊(duì)列:是一個全局串行隊(duì)列,它在應(yīng)用程序的主線程上執(zhí)行任務(wù)漩绵。因?yàn)樗\(yùn)行在您的應(yīng)用程序的主線程上贱案,主隊(duì)列經(jīng)常被用來作為應(yīng)用程序的一個關(guān)鍵的同步點(diǎn)。
創(chuàng)建Dispatch Queue
-
串行隊(duì)列
dispatch_queue_t serial = dispatch_queue_create("serial_queue", 0);
參數(shù)一是隊(duì)列名止吐,用于Debug時進(jìn)行跟蹤宝踪,參數(shù)二是隊(duì)列屬性,創(chuàng)建串行隊(duì)列碍扔,默認(rèn)為0瘩燥,或者設(shè)為DISPATCH_QUEUE_SERIAL。
- 并發(fā)隊(duì)列
-
自定義并發(fā)隊(duì)列
dispatch_queue_t concur = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
-
與創(chuàng)建串行隊(duì)列一樣不同,不過參數(shù)二為DISPATCH_QUEUE_CONCURRENT厉膀。
* 全局并發(fā)隊(duì)列
dispatch_queue_t concur = dispatch_get_global_queue(ISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
參數(shù)一是全局并發(fā)隊(duì)列的優(yōu)先級,參數(shù)二為未來新特性預(yù)留二拐,默認(rèn)為0即可服鹅。全局并發(fā)隊(duì)列分別有四個優(yōu)先級:
DISPATCH_QUEUE_PRIORITY_HIGHT,
DISPATCH_QUEUE_PRIORITY_DEFAULT,
DISPATCH_QUEUE_PRIORITY_LOW,
DISPATCH_QUEUE_PRIORITY_BACKGROUND
-
主隊(duì)列
dispatch_queue_t main = dispatch_get_main_queue();
隊(duì)列與線程的關(guān)系
objc上一篇名為Concurrent Programming: APIs and Challenges的文章中的一張圖很好的描述了隊(duì)列與線程的關(guān)系。
??主隊(duì)列中的任務(wù)都是在主線程中執(zhí)行的百新,自定義的串行隊(duì)列和并發(fā)隊(duì)列中的任務(wù)企软,最終會流入系統(tǒng)的全局隊(duì)列,系統(tǒng)會從線程池中分配相應(yīng)的隊(duì)列去執(zhí)行饭望。
將任務(wù)添加到隊(duì)列
在GCD中仗哨,任務(wù)通常是以block的形式添加到隊(duì)列當(dāng)中的,任務(wù)的添加有兩種方式铅辞,dispatch_sync(同步)和dispatch_async(異步)厌漂,這兩種添加方式?jīng)Q定了隊(duì)列中的任務(wù)之間的制約關(guān)系。
dispatch_sync
??將block提交到隊(duì)列中斟珊,并阻塞當(dāng)前隊(duì)列苇倡,直到block執(zhí)行結(jié)束才回返回到當(dāng)前隊(duì)列,提交的任務(wù)之間以同步方式執(zhí)行倍宾。需要注意的是雏节,Apple關(guān)于dispatch_sync的API文檔中有這樣一句話,As an optimization, this function invokes the block on the current thread when possible高职。作為優(yōu)化钩乍,dispatch_sync阻塞當(dāng)前隊(duì)列后,系統(tǒng)會讓當(dāng)前線程執(zhí)行block語句怔锌。dispatch_async
??不等提交到隊(duì)列的block開始執(zhí)行就立即返回到當(dāng)前線程寥粹。該函數(shù)將block提交到隊(duì)列后变过,系統(tǒng)會從線程池中分配線程依次執(zhí)行隊(duì)列中的block(并發(fā)隊(duì)列分配的線程個數(shù)由系統(tǒng)的狀態(tài)決定),這樣就實(shí)現(xiàn)了異步的執(zhí)行方式涝涤。
代碼示例:
dispatch_queue_t serial = dispatch_queue_create("serial_queue", 0);
dispatch_queue_t concur = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(serial, ^{
NSLog(@"%@-sync",[NSThread currentThread]);
});
dispatch_async(serial, ^{
NSLog(@"%@-async",[NSThread currentThread])
});
dispatch_main();
以上是對于GCD的基本使用媚狰,有關(guān)GCD的具體使用可以參考官方文檔Dispatch Queue以及官方API參考Dispatch
通過例子我們來看一下,串行隊(duì)列與并發(fā)隊(duì)列的線程數(shù)目阔拳。
代碼示例:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
dispatch_queue_t serial = dispatch_queue_create("serial_queue", 0);
dispatch_queue_t concur = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 5; i++)
{
dispatch_sync(serial, ^{
NSLog(@"serial-%@-sync",[NSThread currentThread]);
});
dispatch_async(serial, ^{
NSLog(@"serial-%@-async",[NSThread currentThread]);
});
}
for(int i = 0; i < 5; i++)
{
dispatch_sync(concur, ^{
NSLog(@"concurrent-%@-sync",[NSThread currentThread]);
});
dispatch_async(concur, ^{
NSLog(@"concurrent-%@-async",[NSThread currentThread]);
});
}
dispatch_main();
}
return 0;
}
為了結(jié)果直觀顯示崭孤,將不測的部分注釋后運(yùn)行,結(jié)果如下:
-
串行隊(duì)列同步添加
2016-08-14 18:20:30.313 GCD[798:89819] Hello, World! 2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync 2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync 2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync 2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync 2016-08-14 18:20:30.315 GCD[798:89819] serial-<NSThread: 0x100602c80>{number = 1, name = main}-sync Program ended with exit code: 0
-
串行隊(duì)列異步添加
2016-08-14 18:24:36.657 GCD[830:92007] Hello, World! 2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async 2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async 2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async 2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async 2016-08-14 18:24:36.659 GCD[830:92026] serial-<NSThread: 0x100203d20>{number = 2, name = (null)}-async
-
并發(fā)隊(duì)列同步添加
2016-08-14 18:28:53.662 GCD[862:94207] Hello, World! 2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync 2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync 2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync 2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync 2016-08-14 18:28:53.663 GCD[862:94207] concurrent-<NSThread: 0x100502c80>{number = 1, name = main}-sync
-
并發(fā)隊(duì)列異步添加
2016-08-14 18:29:39.743 GCD[872:94931] concurrent-<NSThread: 0x100500050>{number = 3, name = (null)}-async 2016-08-14 18:29:39.743 GCD[872:94932] concurrent-<NSThread: 0x100700560>{number = 4, name = (null)}-async 2016-08-14 18:29:39.743 GCD[872:94933] concurrent-<NSThread: 0x1007006f0>{number = 5, name = (null)}-async 2016-08-14 18:29:39.743 GCD[872:94930] concurrent-<NSThread: 0x100403380>{number = 2, name = (null)}-async 2016-08-14 18:29:39.743 GCD[872:94928] concurrent-<NSThread: 0x100500550>{number = 6, name = (null)}-async
對比以上結(jié)果糊肠,可以得出結(jié)論:
- 隊(duì)列的類型決定所需要的線程的個數(shù)辨宠。串行隊(duì)列和并發(fā)隊(duì)列執(zhí)行任務(wù)所需要的線程個數(shù)不同,串行隊(duì)列只需要一個線程货裹,并發(fā)隊(duì)列需要多個線程(線程個數(shù)由系統(tǒng)決定)嗤形。
- dispatch_sync和dispatch_async決定了是否開啟線程執(zhí)行隊(duì)列中的任務(wù)。dispatch_sync在當(dāng)前線程中執(zhí)行隊(duì)列中的任務(wù)弧圆,dispatch_async從線程池中至少獲取一個線程來赋兵,保證異步執(zhí)行。
- 無論是串行隊(duì)列搔预,還是并發(fā)隊(duì)列霹期,使用dispatch_sync添加任務(wù)后,任務(wù)都是在當(dāng)前線程中執(zhí)行拯田,這是由于Apple公司優(yōu)化的結(jié)果经伙。
- 對于串行隊(duì)列使用dispatch_async,系統(tǒng)會新開一個線程來執(zhí)行隊(duì)列中的任務(wù)勿锅,并沒有開多個線程,可以理解為因?yàn)槿蝿?wù)一次只執(zhí)行一個枣氧,若每個任務(wù)都創(chuàng)建一個線程運(yùn)行溢十,開銷巨大,并不劃算达吞,干脆使用同一線程依次執(zhí)行所有任務(wù)张弛。
- 對于并發(fā)隊(duì)列使用dispatch_async,由于隊(duì)列中的任務(wù)都需要并發(fā)運(yùn)行酪劫,如果只開一個線程吞鸭,則隊(duì)列中的任務(wù)之間相當(dāng)于串行執(zhí)行,所以開了多個線程覆糟,至于線程開多少個這由系統(tǒng)決定刻剥,例如將for循環(huán)的次數(shù)改為100,可以看到有多個任務(wù)使用同一線程滩字。