簡介
GCD(Grand Central Dispatch)是在macOS10.6提出來的,后來在iOS4.0被引入。GCD的引入主要是它的使用比傳統(tǒng)的多線程方案如NSThread、NSOperationQueue、NSInvocationOperation使用起來更加方便,并且GCD的運(yùn)作是在系統(tǒng)級實(shí)現(xiàn)的。由于是作為系統(tǒng)的一部分來實(shí)現(xiàn)的,因此比以前的線程更加有效。
同時GCD使用了block語法,在書寫上變得更加簡潔。
至于什么是多線程,多線程編程的優(yōu)缺點(diǎn)這里就不探討了,主要討論一下GCD的使用。
Dispatch Queue介紹
關(guān)于GCD,蘋果所給出最直接的描述是:將想要執(zhí)行的任務(wù)添加到Dispatch Queue中。因此Dispatch Queue將是接下來討論的關(guān)鍵。
先來看下面這段代碼:
dispatch_async(queue, ^{
// 要執(zhí)行的任務(wù)
});
dispatch_async()是向隊列中添加任務(wù)的函數(shù)。這段代碼是將要執(zhí)行的任務(wù)以block代碼塊的形式作為參數(shù),添加到queue的隊列中,而queue則會按照順序處理隊列中的任務(wù)。
另外,Dispatch Queue以處理方式的不同,分為兩種:
Serial Dispatch Queue,順序依次執(zhí)行,只有隊列中前一個任務(wù)執(zhí)行完成,后一個才可以開始。也就是我們常說的串行隊列。
Concurrent Dispatch Queue,并發(fā)執(zhí)行,將隊列中的任務(wù)依次添加到并行的線程中,同時執(zhí)行。也就是我們常說的并行隊列。??注意:能夠同時執(zhí)行任務(wù)的個數(shù)取決于系統(tǒng)當(dāng)前的處理能力。

??注意:Dispatch Queue隊列并不是指我們印象中的線程,它是任務(wù)隊列,它只負(fù)責(zé)任務(wù)的管理調(diào)度,并不進(jìn)行任務(wù)的執(zhí)行操作,任務(wù)的執(zhí)行是由Dispatch Queue分配的線程來完成的
Dispatch Queue創(chuàng)建
在了解了什么是Dispatch Queue后,來看一下Dispatch Queue是如何得到的,先來看一段代碼:
dispatch_queue_t aSerialDispatchQueue =
dispatch_queue_create("MySerialDispatchQueue", NULL);
這段代碼就是通過dispatch_queue_create()函數(shù)得到一個Dispatch Queue。
其中,第一個參數(shù)是指Dispatch Queue的名稱,可以設(shè)置為NULL但是不建議這樣做,因?yàn)樵赬code和Instruments調(diào)試的時候都會以設(shè)置的這個參數(shù)作為展示名稱,所以建議創(chuàng)建的每一個Dispatch Queue都設(shè)置一個合適的名稱;
函數(shù)的第二個參數(shù)設(shè)置成了NULL,此時得到的是Serial Dispatch Queue類型的隊列,也可以直接設(shè)置第二個參數(shù)為DISPATCH_QUEUE_SERIAL,就像這樣:
dispatch_queue_t aSerialDispatchQueue =
dispatch_queue_create("MySerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
如果我們想得到一個Concurrent Dispatch Queue類型的隊列,第二個參數(shù)要設(shè)置為DISPATCH_QUEUE_CONCURRENT,就像這樣:
dispatch_queue_t aConcurrentDispatchQueue =
dispatch_queue_create("MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
得到的返回值的類型都為dispatch_queue_t。
驗(yàn)證
下面用代碼來驗(yàn)證一下這兩種隊列是否像上邊說的那樣執(zhí)行的。
先來驗(yàn)證一下Serial Dispatch Queue:
dispatch_queue_t serialQueue
= dispatch_queue_create("queue_1", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"任務(wù)1 begin");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"任務(wù)1 stop");
});
dispatch_async(serialQueue, ^{
NSLog(@"任務(wù)2 begin");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"任務(wù)2 stop");
});
dispatch_async(serialQueue, ^{
NSLog(@"任務(wù)3 begin");
[NSThread sleepForTimeInterval:1.f];
NSLog(@"任務(wù)3 stop");
});
看一下打印結(jié)果:

再來驗(yàn)證一下Concurrent Dispatch Queue:
dispatch_queue_t concurrentQueue
= dispatch_queue_create("queue_2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"任務(wù)1 begin");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"任務(wù)1 stop");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"任務(wù)2 begin");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"任務(wù)2 stop");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"任務(wù)3 begin");
[NSThread sleepForTimeInterval:1.f];
NSLog(@"任務(wù)3 stop");
});
看一下打印結(jié)果:

多個Dispatch Queue之間的關(guān)系
通過上面的驗(yàn)證確實(shí)可以看出Serial Dispatch Queue是串行執(zhí)行、Concurrent Dispatch Queue是并行執(zhí)行的。那如果我們創(chuàng)建多個Serial Dispatch Queue會怎樣呢,這些Serial Dispatch Queue也會按照順序依次執(zhí)行么?不是的,它們之間是并發(fā)執(zhí)行的,也就是說多個Dispatch Queue之間是并發(fā)執(zhí)行

那如果想讓多個Serial Dispatch Queue依然保持串行執(zhí)行怎么辦呢?后邊會繼續(xù)說。
Dispatch Queue持有與釋放
在macOS10.8和iOS6.0以后,GCD已經(jīng)支持ARC模式了,所以無需手動管理Dispatch Queue的持有與釋放。
這里提一下MRC模式下管理Dispatch Queue的兩個函數(shù):
dispatch_retain(aSerialDispatchQueue);
dispatch_release(aSerialDispatchQueue);
系統(tǒng)提供的Dispatch Queue
除了我們手動創(chuàng)建的Dispatch Queue以外,系統(tǒng)還給我們提供了幾個現(xiàn)成的隊列,Main Dispatch Queue和Global Dispatch Queue:
Main Dispatch Queue是在主線程中執(zhí)行的Dispatch Queue。因?yàn)橹骶€程只有一條,并且主線程中的任務(wù)是依次執(zhí)行的,所以Main Dispatch Queue自然是Serial Dispatch Queue類型的隊列,追加到Main Dispatch Queue的任務(wù)都是在主線程RunLoop中執(zhí)行的,像界面更新等一些任務(wù)也都是在這個線程中執(zhí)行。
獲得方法:
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
Global Dispatch Queue是所有應(yīng)用程序都能使用的Concurrent Dispatch Queue類型隊列。Global Dispatch Queue有四個優(yōu)先級分別是:高優(yōu)先級(high priority)、默認(rèn)優(yōu)先級(default priority)、低優(yōu)先級(low priority)、后臺優(yōu)先級(background priority)
// 高優(yōu)先級
dispatch_queue_t globalDispatchQueueHigh
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
// 默認(rèn)優(yōu)先級
dispatch_queue_t globalDispatchQueueDefault
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 低優(yōu)先級
dispatch_queue_t globalDispatchQueueLow
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
// 后臺優(yōu)先級
dispatch_queue_t globalDispatchQueueBackground
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
對于Main Dispatch Queue和Global Dispatch Queue,即使在MRC模式下,也無需考慮持有與釋放問題。即使執(zhí)行dispatch_retain()、dispatch_release()函數(shù)也是不會發(fā)生任何變化的。
Dispatch Queue目標(biāo)隊列
GCD中的dispatch_set_target_queue()函數(shù)可以將一個dispatch_object_t對象設(shè)置到目標(biāo)隊列來處理,上邊說到的dispatch_queue_t都屬于dispatch_object_t對象。
上邊曾說過多個Serial Dispatch Queue之間是并行執(zhí)行的,先來驗(yàn)證一下:
dispatch_queue_t queue1
= dispatch_queue_create("queue_1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2
= dispatch_queue_create("queue_2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3
= dispatch_queue_create("queue_3", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue1, ^{
NSLog(@"任務(wù)1 begin");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"任務(wù)1 stop");
});
dispatch_async(queue2, ^{
NSLog(@"任務(wù)2 begin");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"任務(wù)2 stop");
});
dispatch_async(queue3, ^{
NSLog(@"任務(wù)3 begin");
[NSThread sleepForTimeInterval:1.f];
NSLog(@"任務(wù)3 stop");
});
打印結(jié)果:

通過打印結(jié)果來看,雖然創(chuàng)建的是3個串行Dispatch Queue,但是串行的Dispatch Queue間卻是并行執(zhí)行關(guān)系。
如果我們將創(chuàng)建好的這3個Serial Dispatch Queue隊列添加到一個目標(biāo)隊列中,它們的執(zhí)行順序又會怎樣呢:
dispatch_queue_t targetQueue
= dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue1 = dispatch_queue_create("queue_1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("queue_2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("queue_3", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_set_target_queue(queue3, targetQueue);
dispatch_async(queue1, ^{
NSLog(@"任務(wù)1 begin");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"任務(wù)1 stop");
});
dispatch_async(queue2, ^{
NSLog(@"任務(wù)2 begin");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"任務(wù)2 stop");
});
dispatch_async(queue3, ^{
NSLog(@"任務(wù)3 begin");
[NSThread sleepForTimeInterval:1.f];
NSLog(@"任務(wù)3 stop");
});
打印結(jié)果:

通過打印可以看出,被添加到目標(biāo)隊列里的3個隊列,按照串行順序執(zhí)行。其實(shí)是串行執(zhí)行還是并行執(zhí)行跟目標(biāo)隊列的性質(zhì)有關(guān)。
如果將targetQueue換成一個并行隊列,相信被執(zhí)行的3個隊列必然是并行執(zhí)行關(guān)系,我已經(jīng)做了驗(yàn)證:

繼續(xù),現(xiàn)在我們將目標(biāo)隊列targetQueue換回成Serial Dispatch Queue串行隊列,而將3個被添加的隊列換成Concurrent Dispatch Queue并行隊列,并分別向其中額外再添加2個任務(wù),此時3個被添加隊列中分別包含的任務(wù)是:1-1、1-2、1-3;2-1、2-2、2-3;3-1、3-2、3-3,再來看一下打印結(jié)果:

通過打印結(jié)果發(fā)現(xiàn),所有任務(wù)都是按照串行順序執(zhí)行下來的,被添加的三個并行隊列本身的并行特性被失效了。
還沒有完,如果把目標(biāo)隊列換成并行的Concurrent Dispatch Queue又會怎樣呢?

通過打印結(jié)果可以看出,所有的任務(wù)都被并行執(zhí)行。
通過上面的測試可以看出:無論被添加的是什么、什么隊列,它們所包含的任務(wù)(當(dāng)然這些任務(wù)都是沒有被原所在隊列執(zhí)行的)最終都會按照目標(biāo)隊列的自身性質(zhì)來執(zhí)行,它們的優(yōu)先級也遵循目標(biāo)隊列的優(yōu)先級。
利用dispatch_queue_create()函數(shù)生成的Dispatch Queue不管是Serial Dispatch Queue還是Concurrent Dispatch Queue所使用的都是與Global Dispatch Queue的默認(rèn)優(yōu)先級相同優(yōu)先級的線程,利用dispatch_set_target_queue()函數(shù)我們可以改變它們的優(yōu)先級。
貼一下官方文檔(翻譯不好,只能靠你的英文功力了):

延遲追加任務(wù)
dispatch_queue_t mainDispatchQueue =? dispatch_get_main_queue();
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3* NSEC_PER_SEC);
dispatch_after(time,mainDispatchQueue,^{
// 任務(wù)...
});
以上代碼段是將任務(wù)延遲3秒添加到隊列中,注意的是:是添加到隊列中而不是執(zhí)行。
第一個參數(shù)time是dispatch_time_t類型,該類型值可通過dispatch_time()函數(shù)或dispatch_walltime()函數(shù)得到。
dispatch_time()函數(shù)的含義是獲得從第一個參數(shù)指定的時間開始,經(jīng)過第二個參數(shù)指定的時間長度后的時間。DISPATCH_TIME_NOW表示現(xiàn)在的時間,類型為dispatch_time_t。NSEC_PER_SEC為秒的單位,NSEC_PER_MSEC為毫秒單位。dispatch_walltime()函數(shù)用于計算絕對時間。
Dispatch Group
在實(shí)際應(yīng)用中,經(jīng)常需要在執(zhí)行完一些任務(wù)后,再執(zhí)行某一個特定任務(wù)。如果使用的是Serial Dispatch Queue只需將任務(wù)全部添加到隊列中,然后再在最后追加上想要執(zhí)行的任務(wù)就可以了。但是在使用Concurrent Dispatch Queue類型的隊列或者同時使用多個Dispatch Queue的時候,想實(shí)現(xiàn)這樣的需求就比較困難了。
這時,就要用到Dispatch Group了。下面通過代碼來看一下Dispatch Group是如何使用的:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue, ^{NSLog(@"任務(wù)1");});
dispatch_group_async(group,queue, ^{NSLog(@"任務(wù)2");});
dispatch_group_async(group,queue, ^{NSLog(@"任務(wù)3");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"最后要執(zhí)行的任務(wù)");})
group是通過dispatch_group_create()函數(shù)創(chuàng)建的,類型為dispatch_group_t;
dispatch_group_async()函數(shù)與dispatch_async()函數(shù)相同,都是向隊列中追加任務(wù),不同的是dispatch_group_async()函數(shù)中第一個參數(shù)是指定當(dāng)前任務(wù)屬于哪個Dispatch Group;
dispatch_group_notify()函數(shù)中第一個參數(shù)是指定要監(jiān)視的Dispatch Group,在屬于該group的所有任務(wù)都執(zhí)行完成后會將函數(shù)的第三個參數(shù)任務(wù)追加到第二個參數(shù)隊列中執(zhí)行。
除了添加對任務(wù)的監(jiān)控以外,還可使用等待函數(shù),來看下面一段代碼:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue,^{NSLog(@"任務(wù)1");});
dispatch_group_async(group, queue,^{NSLog(@"任務(wù)2");});
dispatch_group_async(group, queue,^{NSLog(@"任務(wù)3");});
dispatch_group_wait(group, 10*NSEC_PER_SEC);
上面代碼中的dispatch_group_wait()函數(shù)是對所屬group的任務(wù)的處理過程進(jìn)行等待,函數(shù)中第二個參數(shù)代表等待時間,為dispatch_time_t類型。如果在設(shè)置時間內(nèi)所有任務(wù)執(zhí)行完成函數(shù)返回long類型的值0,如果返回值不為0說明還有任務(wù)在執(zhí)行中。如果第二個參數(shù)設(shè)置為DISPATCH_TIME_FOREVER,函數(shù)必將返回0,因?yàn)樵摵瘮?shù)將無限期掛起等待,直到所有任務(wù)執(zhí)行完成函數(shù)才會返回。
那么等待到底意味著什么?這意味著一旦調(diào)用dispatch_group_wait()函數(shù),該函數(shù)就處于調(diào)用狀態(tài)而不返回,即執(zhí)行dispatch_group_wait()函數(shù)的所在線程停止。當(dāng)該函數(shù)返回值后,當(dāng)前線程繼續(xù)。
如果將函數(shù)中第二個參數(shù)設(shè)置為DISPATCH_TIME_NOW,則不需要等待即可判定所屬group的任務(wù)是否全部執(zhí)行完成。
dispatch_barrier_async()函數(shù)
通常在進(jìn)行數(shù)據(jù)讀、寫操作的時候,多個任務(wù)同時執(zhí)行讀操作是可以的,但是多個任務(wù)同時執(zhí)行寫操作可能就會發(fā)生數(shù)據(jù)競爭的問題。尤其在一系列復(fù)雜的讀寫操作中,使用Serial Dispatch Queue會導(dǎo)致讀操作效率變低,使用Concurrent Dispatch Queue不但會引起多個寫任務(wù)發(fā)生數(shù)據(jù)競爭,還可能因?yàn)椴l(fā)執(zhí)行導(dǎo)致讀寫順序錯亂。
因此要使用dispatch_barrier_async()函數(shù)配合Concurrent Dispatch Queue并行隊列來解決這個問題。
來看下面一段代碼:
dispatch_queue_t? queue = dispatch_create_queue("OneConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, block_mission1_reading);
dispatch_async(queue, block_mission2_reading);
dispatch_async(queue, block_mission3_reading);
dispatch_async(queue, block_mission4_reading);
dispatch_barrier_async(queue, block_mission5_writing);
dispatch_async(queue, block_mission6_reading);
dispatch_async(queue, block_mission7_reading);
dispatch_async(queue, block_mission8_reading);
dispatch_barrier_async()函數(shù)會等到
block_mission1_reading、
block_mission2_reading、
block_mission3_reading、
block_mission4_reading這些任務(wù)并行執(zhí)行完畢后再將block_mission5_writing任務(wù)追加到隊列中,當(dāng)dispatch_barrier_async()函數(shù)追加的任務(wù)執(zhí)行完成,隊列會恢復(fù)為一般動作,繼續(xù)并行處理后續(xù)追加到隊列中的任務(wù)。

Dispatch Queue掛起與恢復(fù)
當(dāng)我們想掛起某一個Dispatch Queue時
dispatch_suspend(queue);
恢復(fù)
dispatch_resume(queue);
當(dāng)Dispatch Queue掛起后,追加到隊列中但還沒有執(zhí)行的任務(wù)在這之后停止執(zhí)行,恢復(fù)后這些任務(wù)繼續(xù)執(zhí)行。
指定任務(wù)只執(zhí)行一次
通過dispatch_once()函數(shù)指定的任務(wù)只執(zhí)行一次,像單例的初始化就可以用該函數(shù)來實(shí)現(xiàn)。
正常我們書寫單例的方法是:
static NSObject obj = nil;
@synchronized (self) {
if (obj == nil) {
obj = ...
}
}
使用dispatch_once()函數(shù)的實(shí)現(xiàn)方式是:
static NSObject obj = nil;
static dispatch_once_t pred;
dispatch_once( &pred, ^{
obj = ...
});
使用dispatch_once()函數(shù)可以保證在多線程環(huán)境下百分之百安全。
dispatch_sync()與dispatch_async()的區(qū)別
前面使用頻率特別高的添加任務(wù)函數(shù)dispatch_async(),該函數(shù)是非同步的,它只負(fù)責(zé)將任務(wù)添加到隊列中,并不在乎添加到隊列中的任務(wù)是否處理完成,立刻返回。
而相對于dispatch_async()函數(shù)的dispatch_sync()函數(shù)是同步的,dispatch_sync()函數(shù)不但負(fù)責(zé)將任務(wù)添加到隊列中,還要等待添加的任務(wù)執(zhí)行完成再返回,在此過程中調(diào)用dispatch_sync()函數(shù)所在的線程被掛起,直到dispatch_sync()函數(shù)返回,線程恢復(fù),注意是調(diào)用dispatch_sync()函數(shù)的線程被掛起。
關(guān)于dispatch_sync()函數(shù)比較重要的一個問題就是死鎖,為什么會出現(xiàn)死鎖的情況呢?比如說有一個串行隊列,并且dispatch_sync()函數(shù)的調(diào)用也是在該隊列中,這樣串行隊列的線程在調(diào)用dispatch_sync()函數(shù)的時候被掛起,而線程被掛起之后dispatch_sync()函數(shù)添加的任務(wù)一直得不到線程的處理,一直不能返回,所以線程將一直處于被掛起的狀態(tài)。
出現(xiàn)這種狀況的核心問題就是(可能有點(diǎn)繞):調(diào)用dispatch_sync()函數(shù)的線程(注意是線程,而不是隊列,并行隊列有多個線程可能并不會發(fā)生這種狀況,除非調(diào)用函數(shù)的任務(wù)和函數(shù)追加的任務(wù)被分配到并行隊列中同一線程中去)和處理函數(shù)追加的任務(wù)的線程是同一個線程。此時就會發(fā)生死鎖。
舉兩個例子體會一下:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{NSLog(@"任務(wù)");});
// 死鎖
dispatch_queue_t queue = dispatch_queue_create("OneSerialDispatchQueue", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{NSLog(@"任務(wù)");});
});
// 死鎖
dispatch_apply()函數(shù)
該函數(shù)作用是一次性向隊列中添加多個任務(wù),并且跟dispatch_sync()函數(shù)的使用方式一致,是同步的,只有向隊列中添加的所有任務(wù)都執(zhí)行完成才返回。并且dispatch_apply()函數(shù)向隊列中追加的block任務(wù)都是帶有參數(shù)的,這是為了函數(shù)將添加序號作為參數(shù)傳遞給block任務(wù)。
看下面一段代碼:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(8, queue, ^(size_t index){
NSLog(@"這是第%zu任務(wù)", index);
});
NSLog(@"所有任務(wù)處理完成");
執(zhí)行結(jié)果:

雖然我這里的執(zhí)行結(jié)果是順序的,但也有可能執(zhí)行的結(jié)果是無序的,因?yàn)檫@里使用的是并行隊列。但無論前8個任務(wù)的順序是怎樣所有任務(wù)處理完成這個任務(wù)一定是最后一個執(zhí)行。
相信理解了同步概念就一定會明白其中原因了。
< 轉(zhuǎn)載 >