在軟件開發(fā)中使用多線程可以大大地提高用戶體驗,提高效率。Grand Central Dispatch(CGD)則是C語言的一套多線程開發(fā)框架,相比NSThread和NSOperation,GCD更加高效,并且線程由系統(tǒng)管理,會自動運行多核運算。因為這些優(yōu)勢,GCD是Apple推薦給開發(fā)者使用的首選多線程解決方案。
1、GCD的調度機制
GCD框架中一個很重要的概念是調度隊列,我們對線程的操作實際上是由調度隊列完成的。我們只需要將要執(zhí)行的任務添加到合適的隊列中即可。在GCD框架中,有如下三種類型的調度隊列。
1.1主隊列
其中的任務在主線程中執(zhí)行,因為其會阻塞主線程,所以是一個串行的隊列。可以通過下面的方法得到:
dispatch_get_main_queue();
1.2全局并行隊列
隊列中任務的執(zhí)行嚴格按照先進先出的模式進行。如果是串行的隊列,則當一個任務結束后,才會開啟另一個任務,如果是并行隊列,則任務的開啟順序和添加順序是一致的。系統(tǒng)為iOS應用自動創(chuàng)建了4個全局共享的并發(fā)隊列。使用下面的函數(shù)獲得:
dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>);
上面函數(shù)的第一個參數(shù)是這個隊列的ID,系統(tǒng)的4個全局隊列默認的優(yōu)先級不同,這個參數(shù)可填寫的定義如下:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 //優(yōu)先級別最高的全局隊列
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0//優(yōu)先級別中等的全局隊列
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)//優(yōu)先級別較低的全局隊列
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN//后臺的全局隊列,優(yōu)先級別最低
這個函數(shù)的第二個參數(shù)是一個預留參數(shù),我們可以傳NULL.
1.3自定義隊列
上面的兩種隊列都是系統(tǒng)為我們創(chuàng)建好的,我們只需要獲取到他們,添加任務即可。當然我們也可以創(chuàng)建自己的隊列,包含串行和并行的。使用如下方法來創(chuàng)建:
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
其中第一個參數(shù)是這個隊列的名字,第二個參數(shù)決定創(chuàng)建的是串行還是并行隊列。填寫DISPATCH_QUEUE_SERIAL或NULL創(chuàng)建串行隊列,填寫DISPATCH_QUEUE_CONCURRENT創(chuàng)建并行隊列。
2、添加任務到調度隊列中
使用dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)函數(shù)或者dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)函數(shù)來同步或異步的執(zhí)行任務。示例如下:
- (void)creatGCDQueue {
//創(chuàng)建一個串行的隊列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
//向隊列中添加同步任務1
dispatch_sync(queue, ^{
NSLog(@"%@:task1",[NSThread currentThread]);
});
//向隊列中添加異步任務2
dispatch_async(queue, ^{
NSLog(@"%@:task2",[NSThread currentThread]);
});
}
//打印信息:

上面的代碼創(chuàng)建了一個串行的自定義隊列,并且向隊列中添加了一個同步的任務和一個異步的任務。需要注意,這里的同步和異步指的是針對當前代碼運行所在的線程而言的。
從打印信息可以看出,同步的任務是在主線程中執(zhí)行,異步的任務是在單獨的線程中執(zhí)行,由于我們創(chuàng)建的調度隊列是串行的,因此先開啟了任務1,后開啟了任務2.
只有當調度隊列是并行,而且向隊列中添加的任務也是異步的時候,多任務才會實現(xiàn)并行異步執(zhí)行。
實現(xiàn)如下:
- (void)creatGCDQueue {
//創(chuàng)建一個并行的隊列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
//向隊列中添加異步任務1
dispatch_async(queue, ^{
for (int i = 0; i < 15; i ++) {
NSLog(@"%@ = %d:task1",[NSThread currentThread],i);
}
});
//向隊列中添加異步任務2
dispatch_async(queue, ^{
for (int i = 0; i < 15; i ++) {
NSLog(@"%@ = %d:task2",[NSThread currentThread],i);
} });
}
3、使用隊列組
通過前面的學習,我們現(xiàn)在已經(jīng)可以運用隊列多線程執(zhí)行任務了,但是GCD的強大之處遠遠不止如此??聪旅娴睦印?br>
如果有3個任務A、B、C,其中A與B是沒有關系的,他們可以并行執(zhí)行,C必須是A、B都結束之后才能執(zhí)行,當然,實現(xiàn)這樣的邏輯并不困難,使用KVO就可以實現(xiàn),但是如果使用隊列處理這樣的邏輯,則代碼會更加清晰簡單。
可以使用dispatch_group_create()創(chuàng)建一個隊列組,使用如下函數(shù)將隊列添加到隊列組中:
void dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
隊列中的隊列是異步執(zhí)行的,示例如下:
- (void)creatGCDGroup {
//創(chuàng)建一個隊列組
dispatch_group_t group = dispatch_group_create();
//創(chuàng)建一個異步隊列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
//添加任務
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"%@ = %d:task1",[NSThread currentThread],i);
}
});
//添加任務
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"%@ = %d:task2",[NSThread currentThread],i);
}
});
//阻塞線程,直到前面的隊列任務執(zhí)行完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
for (int i = 0; i < 10; i ++) {
NSLog(@"%@ = %d:over",[NSThread currentThread],i);
}
}
打印結果如下:

以上代碼完美的實現(xiàn)了我們的任務依賴需求,可以看出GCD的強大了吧,復雜的任務邏輯關系因為GCD變得十分清晰簡單。
4、GCD對循環(huán)任務的處理
說到循環(huán),除了常規(guī)的while循環(huán),for循環(huán)外,for-in也是開發(fā)中常用的一種循環(huán)方式。for-in循環(huán)通常來進行數(shù)組或字典的遍歷,這種遍歷通常不關心循環(huán)執(zhí)行的順序。使用GCD,配合設備的多核運算技術,我們可以將這種循環(huán)遍歷的性能提升到極致,示例如下:
- (void)creatGCDApply {
dispatch_apply(20, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t i) {
NSLog(@"%@:%zu",[NSThread currentThread],i);
});
}
打印信息如下:

從打印信息可以看出,循環(huán)是由多個不同的線程完成的,比如我們的設備是8核的CPU。因此每個線程單獨在一個核執(zhí)行,這將循環(huán)的運行效率提升到了極致。大大提高了運行速率。
5、GCD中的消息與信號
5.1Dispatch Source
在GCD框架中提供了dispatch_source_t類型的對象,dispatch_source_t類型的對象可以用來傳遞和接收某個消息。在任一線程上調用它的一個函數(shù) dispatch_source_merge_data 后,會執(zhí)行 Dispatch Source 事先定義好的句柄(可以把句柄簡單理解為一個 block )。
這個過程叫 Custom event ,用戶事件。是 dispatch source 支持處理的一種事件。簡單地說,這種事件是由你調用 dispatch_source_merge_data 函數(shù)來向自己發(fā)出的信號。
示例如下:
- (void)creatGCDSource {
//創(chuàng)建一個數(shù)據(jù)對象,DISPATCH_SOURCE_TYPE_DATA_ADD的含義表示當數(shù)據(jù)變化時相加
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
//設置響應分派源事件的block,在分派源指定的隊列上運行
dispatch_source_set_event_handler(source, ^{
NSLog(@"%lu:sec",dispatch_source_get_data(source));//得到分派源的數(shù)據(jù)
dispatch_async(dispatch_get_main_queue(), ^{
//更新UI
});
});
//啟動
dispatch_resume(source);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//網(wǎng)絡請求
//向分派源發(fā)送事件,需要注意的是,不可以傳遞0值(事件不會被觸發(fā)),同樣也不可以傳遞負數(shù)。
dispatch_source_merge_data(source, 1);
});
}
注意:DISPATCH_SOURCE_TYPE_DATA_ADD是將所有觸發(fā)結果相加,最后統(tǒng)一執(zhí)行響應,但是加入sleepForTimeInterval后,如果interval的時間越長,則每次觸發(fā)都會響應,但是如果interval的時間很短,則會將觸發(fā)后的結果相加后統(tǒng)一觸發(fā)。這在更新UI時很有用,比如更新進度條時,沒必要每次觸發(fā)都響應,因為更新時還有其他的用戶操作(用戶輸入,觸碰等),所以可以統(tǒng)一觸發(fā)
比如我們寫一個進度條的示例:
- (void)creatGCDSource {
//1、指定DISPATCH_SOURCE_TYPE_DATA_ADD,做成Dispatch Source(分派源)。設定Main Dispatch Queue 為追加處理的Dispatch Queue
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
__block NSUInteger totalComplete = 0;
dispatch_source_set_event_handler(source, ^{
//當處理事件被最終執(zhí)行時,計算后的數(shù)據(jù)可以通過dispatch_source_get_data來獲取。這個數(shù)據(jù)的值在每次響應事件執(zhí)行后會被重置,所以totalComplete的值是最終累積的值。
NSUInteger value = dispatch_source_get_data(source);
totalComplete += value;
NSLog(@"進度:%@", @((CGFloat)totalComplete/100));
NSLog(@":large_blue_circle:線程號:%@", [NSThread currentThread]);
});
//分派源創(chuàng)建時默認處于暫停狀態(tài),在分派源分派處理程序之前必須先恢復。
dispatch_resume(source);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2、恢復源后,就可以通過dispatch_source_merge_data向Dispatch Source(分派源)發(fā)送事件:
dispatch_async(queue, ^{
for (NSUInteger index = 0; index < 100; index++) {
dispatch_source_merge_data(source, 1);
NSLog(@":recycle:線程號:%@~~~~~~~~~~~~i = %ld", [NSThread currentThread], index);
sleep(0.1);
}
});
}
5.2、信號量 singer
信號量是GCD中一個很重要的概念,他的用法與消息的傳遞有所類似,其本示例代碼如下:
- (void)creatGCDSinger {
//創(chuàng)建一個信號,其中的參數(shù)是信號的初始值
dispatch_semaphore_t singer = dispatch_semaphore_create(0);
//發(fā)送信號,信號量+1
dispatch_semaphore_signal(singer);
//等待信號,當信號量大于0時,執(zhí)行后面的代碼,否則等待,第二個參數(shù)為等待的超時時長,下面設置的為一直等待
dispatch_semaphore_wait(singer, DISPATCH_TIME_FOREVER);
NSLog(@"singer");
}
注意,dispatch_semaphore_wait函數(shù)會阻塞當前線程,在主線程中要慎用。通過發(fā)送信號函數(shù):dispatch_semaphore_signal(),可以使信號量+1,每次執(zhí)行過等待信號后,信號量會-1,如此,我們可以很方便地控制不同隊列中方法的執(zhí)行流程。
5.2.1限制線程的最大并發(fā)數(shù)
- (void)creatGCDSinger {
//創(chuàng)建一個信號,其中的參數(shù)是信號的初始值
dispatch_semaphore_t singer = dispatch_semaphore_create(2);
for (int i = 0; i < 15; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//等待信號,當信號量大于0時,執(zhí)行后面的代碼,否則等待,第二個參數(shù)為等待的超時時長,下面設置的為一直等待
dispatch_semaphore_wait(singer, DISPATCH_TIME_FOREVER);
//doing
sleep(1);
//發(fā)送信號,信號量+1
dispatch_semaphore_signal(singer);
});
}
}
如上述代碼可知,總共異步執(zhí)行15個任務,但是由于我們設置了值為2的信號量,每一次執(zhí)行任務的時候信號量都會先-1,而在任務結束后使信號量加1,當信號量減到0的時候,說明正在執(zhí)行的任務有2個,這個時候其它任務就會阻塞,直到有任務被完成時,這些任務才會執(zhí)行。
注意,信號量的正常的使用順序是先降低(dispatch_semaphore_wait)然后再提高(dispatch_semaphore_signal),這兩個函數(shù)通常成對使用。
5.2.2阻塞發(fā)請求的線程
有些時候,我們需要阻塞發(fā)送請求的線程,比如在多個請求回調后統(tǒng)一操作的需求,而這些請求之間并沒有順序關系,且這些接口都會另開線程進行網(wǎng)絡請求的。一般地,這種多線程完成后進行統(tǒng)一操作的需求都會使用隊列組(dispatch_group_t)來完成,但是由于是異步請求,沒等其異步回調之后,請求的線程就結束了,為此,就需要使用信號量來阻塞住發(fā)請求的線程。實現(xiàn)代碼如下:
- (void)creatGCDSinger {
//創(chuàng)建線程組
dispatch_group_t group = dispatch_group_create();
//獲取隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任務1
dispatch_group_async(group, queue, ^{
//請求1
[self request1];
});
//任務2
dispatch_group_async(group, queue, ^{
//請求2
[self request2];
});
//任務3
dispatch_group_async(group, queue, ^{
//請求3
[self request3];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"-------所有網(wǎng)絡請求已請求完成-------");
});
}
- (void)request1 {
//創(chuàng)建信號量,并設置為0,信號量本質是資源數(shù),為0表示用完,需要等待
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
//模擬網(wǎng)絡請求-異步
//每次網(wǎng)絡請求成功或失敗后,都讓信號量+1,表示釋放當前資源,其他線程可以搶占了
[[KNetRequestManager share] getSomeData:^{
//網(wǎng)絡請求成功,發(fā)送信號
dispatch_semaphore_signal(sema);
} errorBlock:^{
//網(wǎng)絡請求失敗,發(fā)送信號
dispatch_semaphore_signal(sema);
}];
//如果信號量為0,表示沒有資源可用,便一直等待,不再往下執(zhí)行.只有當網(wǎng)絡請求成功或失敗時,才會往下走
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
- (void)request2 {
//創(chuàng)建信號量,并設置為0,信號量本質是資源數(shù),為0表示用完,需要等待
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
//模擬網(wǎng)絡請求-異步
//每次網(wǎng)絡請求成功或失敗后,都讓信號量+1,表示釋放當前資源,其他線程可以搶占了
[[KNetRequestManager share] getSomeData:^{
//網(wǎng)絡請求成功,發(fā)送信號
dispatch_semaphore_signal(sema);
} errorBlock:^{
//網(wǎng)絡請求失敗,發(fā)送信號
dispatch_semaphore_signal(sema);
}];
//如果信號量為0,表示沒有資源可用,便一直等待,不再往下執(zhí)行
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
- (void)request3 {
//創(chuàng)建信號量,并設置為0,信號量本質是資源數(shù),為0表示用完,需要等待
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
//模擬網(wǎng)絡請求-異步
//每次網(wǎng)絡請求成功或失敗后,都讓信號量+1,表示釋放當前資源,其他線程可以搶占了
[[KNetRequestManager share] getSomeData:^{
//網(wǎng)絡請求成功,發(fā)送信號
dispatch_semaphore_signal(sema);
} errorBlock:^{
//網(wǎng)絡請求失敗,發(fā)送信號
dispatch_semaphore_signal(sema);
}];
//如果信號量為0,表示沒有資源可用,便一直等待,不再往下執(zhí)行
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
當然,我們也可以使用dispatch_group_enter和dispatch_group_leave來實現(xiàn)同樣的功能:
- (void)creatGCDSinger {
//創(chuàng)建線程組
dispatch_group_t group = dispatch_group_create();
//創(chuàng)建一個并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("group.queue", DISPATCH_QUEUE_CONCURRENT);
//任務1
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
//請求1
[self request1WithGroup:group];
});
//任務2
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
//請求2
[self request2WithGroup:group];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"-------所有網(wǎng)絡請求已請求完成-------");
});
}
- (void)request1WithGroup:(dispatch_group_t)group {
//模擬網(wǎng)絡請求-異步
[[KNetRequestManager share] getSomeData:^{
//網(wǎng)絡請求成功,調用level
dispatch_group_leave(group);
} errorBlock:^{
//網(wǎng)絡請求失敗,調用level
dispatch_group_leave(group);
}];
}
- (void)request2WithGroup:(dispatch_group_t)group
//模擬網(wǎng)絡請求-異步
[[KNetRequestManager share] getSomeData:^{
//網(wǎng)絡請求成功,調用level
dispatch_group_leave(group);
} errorBlock:^{
//網(wǎng)絡請求失敗,調用level
dispatch_group_leave(group);
}];
}
5.2.3信號量控制網(wǎng)絡請求順序
- (void)creatGCDSinger {
//創(chuàng)建semp
dispatch_semaphore_t semp = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任務1
dispatch_async(queue, ^{
//信號量-1
dispatch_semaphore_wait(semp, DISPATCH_TIME_FOREVER);
//模擬網(wǎng)絡請求
//模擬網(wǎng)絡請求-異步
//每次網(wǎng)絡請求成功或失敗后,都讓信號量+1,表示釋放當前資源,其他線程可以搶占了
[[KNetRequestManager share] getSomeData:^{
//網(wǎng)絡請求成功,發(fā)送信號
dispatch_semaphore_signal(sema);
} errorBlock:^{
//網(wǎng)絡請求失敗,發(fā)送信號
dispatch_semaphore_signal(sema);
}];
});
//任務2
dispatch_async(queue, ^{
//信號量-1
dispatch_semaphore_wait(semp, DISPATCH_TIME_FOREVER);
//模擬網(wǎng)絡請求
//模擬網(wǎng)絡請求-異步
//每次網(wǎng)絡請求成功或失敗后,都讓信號量+1,表示釋放當前資源,其他線程可以搶占了
[[KNetRequestManager share] getSomeData:^{
//網(wǎng)絡請求成功,發(fā)送信號
dispatch_semaphore_signal(sema);
} errorBlock:^{
//網(wǎng)絡請求失敗,發(fā)送信號
dispatch_semaphore_signal(sema);
}];
});
}
6、隊列的掛起和開啟
在GCD框架中還提供了暫停與開始任務隊列的方法,使用下面的函數(shù)可以將隊列或隊列組暫時掛起和開啟:
//掛起隊列或隊列組
void dispatch_suspend(dispatch_object_t object);
//開啟隊列或隊列組
void dispatch_resume(dispatch_object_t object);
注意:在暫停隊列時,隊列中正在執(zhí)行的任務并不會中斷,未開啟的任務會被掛起。
7、數(shù)據(jù)存儲的線程安全問題-多度單寫
在進行多線程編程時,或許總會遇到這一類問題:數(shù)據(jù)的競爭與線程的安全。這些問題如果通過程序手動來控制,則難度將會非常大。CGD同樣為我們簡單地解決了這樣的問題。
首先,如果只是在讀取數(shù)據(jù),而不對數(shù)據(jù)做任何修改,那么我們并不需要處理安全問題,可以讓多個任務同時進行讀取??墒侨绻獙?shù)據(jù)進行寫操作,那么在同一時間,我們就必須只能有一個任務在寫,CGD中有一個方法幫我們完美地解決了這個問題,示例如下:
- (void)creatCGDReadAndWriter {
//創(chuàng)建一個隊列
dispatch_queue_t queue = dispatch_queue_create("oneQueue", DISPATCH_QUEUE_CONCURRENT);
//多個任務同時執(zhí)行讀操作
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"read1:%d",i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"read2:%d",i);
}
});
//執(zhí)行寫操作
/*
下面這個函數(shù)在加入隊列時不會執(zhí)行,會等待已經(jīng)開始的異步執(zhí)行全部完成后再執(zhí)行,并且在執(zhí)行時會阻塞其他任務
當執(zhí)行完成后,其他任務重新進入異步執(zhí)行
*/
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 5; i ++) {
NSLog(@"writer:%d",i);
}
});
//績效執(zhí)行異步操作
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"read3:%d",i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"read4:%d",i);
}
});
}
打印信息:

從打印信息可以看出讀操作是異步進行的,寫操作是等待當前任務結束后阻塞任務隊列獨立進行的,當寫操作結束后隊列恢復異步執(zhí)行讀操作,這正是我們需要的效果。