GCD簡(jiǎn)介
- GCD全稱:
Grand Central Dispatch,譯為大型的中樞調(diào)度器 - 純C語(yǔ)言實(shí)現(xiàn),提供了非常多強(qiáng)大的功能
- GCD的優(yōu)勢(shì)
- GCD是蘋(píng)果公司為
多核的并行運(yùn)算提出的解決方案 - GCD會(huì)
自動(dòng)利用更多的CPU內(nèi)核(如:雙核、四核) - GCD會(huì)
自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程) - 程序猿只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫(xiě)任何管理線程的代碼
- GCD是蘋(píng)果公司為
- GCD的兩個(gè)核心概念:
任務(wù)和隊(duì)列- 任務(wù):執(zhí)行什么操作
- 隊(duì)列:用來(lái)存放任務(wù),分為:并行隊(duì)列和串行隊(duì)列
并行隊(duì)列 和 串行隊(duì)列
- 隊(duì)列本質(zhì):用于控制任務(wù)的執(zhí)行方式
- 并行隊(duì)列
- 英文:
ConcurrentDispatch Queue - 可以讓多個(gè)任務(wù)
并發(fā)執(zhí)行,以提高執(zhí)行效率 - 并發(fā)功能
僅在異步(dispatch_async)函數(shù)下才有效
- 英文:
- 串行隊(duì)列
- 英文:
SerialDispatch Queue - 在當(dāng)前線程中讓任務(wù)
一個(gè)接著一個(gè)地執(zhí)行
- 英文:
- 創(chuàng)建隊(duì)列
// 隊(duì)列類型
dispatch_queue_t
// 第一個(gè)參數(shù):隊(duì)列名稱
// 第二個(gè)參數(shù):隊(duì)列類型
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
/** 隊(duì)列類型
// 串行隊(duì)列標(biāo)識(shí):本質(zhì)就是NULL,但建議不要寫(xiě)成NULL,可讀性不好
DISPATCH_QUEUE_SERIAL
// 并行隊(duì)列標(biāo)識(shí)
DISPATCH_QUEUE_CONCURRENT
*/
- 創(chuàng)建
并行隊(duì)列的兩種方式- 直接創(chuàng)建
dispatch_queue_t queue = dispatch_queue_create("yanhooQueue", DISPATCH_QUEUE_CONCURRENT);- 獲取
全局并發(fā)隊(duì)列
// 第一個(gè)參數(shù):隊(duì)列優(yōu)先級(jí) // 第二個(gè)參數(shù):保留參數(shù),暫時(shí)無(wú)用,用0即可 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 全局并發(fā)隊(duì)列的優(yōu)先級(jí) #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)(中) #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺(tái) - 創(chuàng)建
串行隊(duì)列的兩種方式- 直接創(chuàng)建
// 創(chuàng)建串行隊(duì)列(隊(duì)列類型傳遞DISPATCH_QUEUE_SERIAL或者NULL) dispatch_queue_t queue = dispatch_queue_create("yanhooQueue", DISPATCH_QUEUE_SERIAL);- 獲取主隊(duì)列:主隊(duì)列是一種特殊的
串行隊(duì)列
// 主隊(duì)列中的任務(wù),都會(huì)放到主線程中執(zhí)行 dispatch_queue_t queue = dispatch_get_main_queue();
同步(sync)函數(shù) 和 異步(async)函數(shù)
- 函數(shù)作用:將
任務(wù)添加到隊(duì)列中 - 函數(shù)類型:決定是否有
開(kāi)啟新線程的能力 - 同步函數(shù):
不具備開(kāi)啟新線程的能力,只能在當(dāng)前線程中執(zhí)行任務(wù)
// queue:隊(duì)列
// block:任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- 異步函數(shù):
具備開(kāi)啟線程的能力,但不一定開(kāi)啟新線程,比如:當(dāng)前隊(duì)列為主隊(duì)列,異步函數(shù)也不會(huì)開(kāi)啟新的線程
// queue:隊(duì)列
// block:任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 經(jīng)驗(yàn)總結(jié):
- 通過(guò)
異步函數(shù)添加任務(wù)到隊(duì)列中,任務(wù)不會(huì)立即執(zhí)行 - 通過(guò)
同步函數(shù)添加任務(wù)到隊(duì)列中,任務(wù)會(huì)立即執(zhí)行
- 通過(guò)
程序猿只需要做下列事情
- 指定函數(shù)類型:是否具備
開(kāi)啟新線程的能力 - 指定隊(duì)列類型:決定任務(wù)的
執(zhí)行方式 - 確定要執(zhí)行的任務(wù),并
通過(guò)函數(shù)將任務(wù)添加到隊(duì)列中,任務(wù)的執(zhí)行遵循隊(duì)列的FIFO原則:先進(jìn)先出,后進(jìn)后出 - 剩下的事情就交給GCD來(lái)完成了
函數(shù)和隊(duì)列組合后的執(zhí)行效果

Thread_3.png
- 異步函數(shù) + 并行隊(duì)列
- 開(kāi)啟
多條子線程,任務(wù)并行執(zhí)行 - 代碼展示
- 開(kāi)啟
- (void)asyncConcurrent
{
// 1.創(chuàng)建并行隊(duì)列
// dispatch_queue_t queue = dispatch_queue_create("yanhooQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.通過(guò)異步函數(shù)將將任務(wù)加入隊(duì)列
dispatch_async(queue, ^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"1-----%@", [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"2-----%@", [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"3-----%@", [NSThread currentThread]);
}
});
// 證明:異步函數(shù)添加任務(wù)到隊(duì)列中,任務(wù)【不會(huì)】立即執(zhí)行
NSLog(@"asyncConcurrent--------end");
// 釋放隊(duì)列,ARC中無(wú)需也不允許調(diào)用這個(gè)方法
// dispatch_release(queue);
}
- 異步函數(shù) + 串行隊(duì)列
- 開(kāi)啟
一條子線程,任務(wù)是有序的在子線程上執(zhí)行 - 代碼展示
- 開(kāi)啟
- (void)asyncSerial
{
// 1.創(chuàng)建串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("yanhooQueue", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t queue = dispatch_queue_create("yanhooQueue", NULL);
// 2.通過(guò)異步函數(shù)將任務(wù)加入隊(duì)列
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
// 證明:異步函數(shù)添加任務(wù)到隊(duì)列中,任務(wù)【不會(huì)】立馬執(zhí)行
NSLog(@"asyncConcurrent--------end");
}
- 異步函數(shù) + 主隊(duì)列
-
不開(kāi)啟子線程,任務(wù)是在主線程中有序執(zhí)行 - 代碼展示
-
- (void)asyncMain
{
// 1.獲得主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.通過(guò)異步函數(shù)將任務(wù)加入隊(duì)列
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
}
- 同步函數(shù) + 并行隊(duì)列
-
不會(huì)開(kāi)啟子線程,任務(wù)是有序執(zhí)行 - 代碼展示
-
- (void)syncConcurrent
{
// 1.獲得全局的并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.通過(guò)同步函數(shù)將任務(wù)加入隊(duì)列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
// 證明:同步函數(shù)添加任務(wù)到隊(duì)列中,任務(wù)【立馬執(zhí)行】
NSLog(@"syncConcurrent--------end");
}
- 同步函數(shù) + 串行隊(duì)列
-
不會(huì)開(kāi)啟線程,任務(wù)是有序執(zhí)行 -
易發(fā)生死鎖,使用時(shí)要注意 - 下面的用法會(huì)發(fā)生死鎖嗎?
-
- (void)syncMain
{
// 1.創(chuàng)建串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("yanhooQueue", DISPATCH_QUEUE_SERIAL);
// 2.將任務(wù)加入隊(duì)列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
}
- 答案:上面的用法不會(huì)發(fā)生死鎖,原因分析如下:
- 雖然都是在主線程上執(zhí)行的,但任務(wù)在不同的隊(duì)列中所以不會(huì)發(fā)生阻塞
-
syncMain函數(shù)是在主隊(duì)列中 - 其他的任務(wù)是在
新建的串行隊(duì)列中
- 死鎖的幾中場(chǎng)景
- 場(chǎng)景1
dispatch_queue_t queue = dispatch_queue_create("yanhooQueue", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ NSLog(@"1-----%@", [NSThread currentThread]); // 這里阻塞了 dispatch_sync(queue, ^{ NSLog(@"2-----%@", [NSThread currentThread]); }); });- 場(chǎng)景2
- (void)syncMain { // 獲得主隊(duì)列 dispatch_queue_t queue = dispatch_get_main_queue(); // 這里阻塞了 dispatch_sync(queue, ^{ NSLog(@"1-----%@", [NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"2-----%@", [NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"3-----%@", [NSThread currentThread]); }); }- 原因分析
- 使用
同步函數(shù)在任務(wù)執(zhí)行過(guò)程中往任務(wù)所在的串行隊(duì)列中添加任務(wù)就會(huì)導(dǎo)致任務(wù)間互相等待,造成死鎖 - 別忘了
同步函數(shù)添加任務(wù)到隊(duì)列中,任務(wù)會(huì)立即執(zhí)行,如果是異步函數(shù)就不會(huì)發(fā)生死鎖
- 使用
GCD實(shí)現(xiàn)線程間通信
// 全局并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 異步函數(shù)
dispatch_async(queue, ^
{
// 執(zhí)行耗時(shí)的任務(wù)
coding...
// 【標(biāo)記1】回到主線程,執(zhí)行UI刷新操作
dispatch_async(dispatch_get_main_queue(), ^
{
coding...
// 還可以嵌套:再回到子線程做其他事情
dispatch_async(queue, ^
{
coding...
});
});
// 后續(xù)代碼
coding....
});
- 【標(biāo)記1】處也可以用
同步函數(shù)回到主線程,但是同步函數(shù)會(huì)導(dǎo)致添加的新任務(wù)立即執(zhí)行,導(dǎo)致必須等添加到主隊(duì)列的任務(wù)執(zhí)行完才會(huì)繼續(xù)執(zhí)行,也不是不能這么用,看具體場(chǎng)景是否需要等待主隊(duì)列的任務(wù)執(zhí)行完畢才繼續(xù)往后執(zhí)行
GCD中其他常用函數(shù)
dispatch_once_t
- 函數(shù)作用:保證某段代碼在程序運(yùn)行過(guò)程中
只被執(zhí)行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這個(gè)函數(shù)本身是【線程安全】的)
});
dispatch_after和dispatch_time_t
- 函數(shù)作用:延遲將任務(wù)提交到隊(duì)列中,
不要理解成延遲執(zhí)行任務(wù)
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));
dispatch_after(time, queue, ^
{
// 此任務(wù)被延遲提交到隊(duì)列中
});
- dispatch_time_t
- 第一個(gè)參數(shù)一般是DISPATCH_TIME_NOW,表示從現(xiàn)在開(kāi)始
- 第二個(gè)參數(shù)就是真正的
延時(shí)時(shí)間,單位為納秒 - 關(guān)于
NSEC_PER_SEC的解釋可以查看我這篇文章
dispatch_suspend 和 dispatch_resume
- dispatch_suspend
- 函數(shù)作用:
只能掛起隊(duì)列中還未執(zhí)行的任務(wù),正在運(yùn)行的任務(wù)是無(wú)法掛起的
- 函數(shù)作用:
- dispatch_resume
- 函數(shù)作用:只能
恢復(fù)隊(duì)列中還未執(zhí)行的任務(wù)
- 函數(shù)作用:只能
dispatch_apply
- 此函數(shù)和
dispatch_sync函數(shù)一樣,會(huì)等待處理結(jié)束,所以建議在dispatch_async中使用此函數(shù) - 此函數(shù)必須結(jié)合
并行隊(duì)列才能發(fā)揮作用 - 函數(shù)作用:可以快速完成
對(duì)順序沒(méi)有要求的集合遍歷,因?yàn)?code>執(zhí)行順序不確定 - 使用說(shuō)明
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index)
{
// 執(zhí)行10次代碼,會(huì)開(kāi)啟多條線程來(lái)執(zhí)行任務(wù),執(zhí)行順序不確定
});
- 示例:文件剪切
- (void)applyDemo
{
NSString *from = @"/Users/xxx/Desktop/From";
NSString *to = @"/Users/xxx/Desktop/To";
NSFileManager *mgr = [NSFileManager defaultManager];
NSArray *subpaths = [mgr subpathsAtPath:from];
// 并行隊(duì)列才會(huì)起作用
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(subpaths.count, queue, ^(size_t index) {
NSString *subpath = subpaths[index];
NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
// 剪切
[mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
NSLog(@"%@---%@", [NSThread currentThread], subpath);
});
}
dispatch_barrier_async
-
必須是并行隊(duì)列,且不能使用全局的并行隊(duì)列,實(shí)踐證明不管用 - 函數(shù)作用:在此函數(shù)前面的任務(wù)執(zhí)行完成后此函數(shù)才開(kāi)始執(zhí)行,在此函數(shù)后面的任務(wù)等此函數(shù)執(zhí)行完成后才會(huì)執(zhí)行
- (void)barrierDemo
{
//【不能】使用全局并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("yanhooQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
// 在它前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,在它后面的任務(wù)等它執(zhí)行完成后才會(huì)執(zhí)行
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
}
dispatch_group
- 必須是
并行隊(duì)列才起作用 - 需求描述
- 現(xiàn)有三個(gè)任務(wù):任務(wù)A、任務(wù)B、任務(wù)C
- 任務(wù)C需要等到任務(wù)A和任務(wù)B都完成后才執(zhí)行
- 任務(wù)A和任務(wù)B執(zhí)行沒(méi)有先后順序
- 使用
dispatch_group可以實(shí)現(xiàn)上面的需求 - 創(chuàng)建dispatch_group_t
// 創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
- 添加任務(wù)分兩種情況
- 自己可以控制并創(chuàng)建隊(duì)列,使用dispatch_group_async
// 省去創(chuàng)建group、queue代碼...... dispatch_group_async(group, queue, ^{ // 添加任務(wù)A到group }); dispatch_group_async(group, queue, ^{ // 添加任務(wù)B到group });- 無(wú)法控制隊(duì)列,即使用的隊(duì)列不是你創(chuàng)建的(如:
AFNetworking異步添加任務(wù)),此時(shí)可以使用dispatch_group_enter,dispatch_group_leave控制任務(wù)的執(zhí)行順序
// 使用dispatch_group_enter,dispatch_group_leave可以方便的將一系列網(wǎng)絡(luò)請(qǐng)求打包起來(lái) AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; // 添加任務(wù)A到group // ---打標(biāo)記--- dispatch_group_enter(group); [manager GET:@"http://www.baidu.com" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { // do something // ---刪除標(biāo)記--- dispatch_group_leave(group); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { // do something // ---刪除標(biāo)記--- dispatch_group_leave(group); }]; // 添加任務(wù)B到group類似上面的操作 - 添加結(jié)束任務(wù)也分為兩種情況
- dispatch_group_notify(
推薦):不會(huì)阻塞當(dāng)前線程,馬上返回
dispatch_group_notify(group, dispatch_get_main_queue(), ^ { // do something });- dispatch_group_wait(
不推薦):阻塞當(dāng)前線程,直到dispatch group中的所有任務(wù)完成才會(huì)返回
// 第二個(gè)參數(shù)是超時(shí)時(shí)間 dispatch_group_wait(group, DISPATCH_TIME_FOREVER); - dispatch_group_notify(
- 完整示例
// 創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
// 獲取全局并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 添加任務(wù)A到group
dispatch_group_async(group, queue, ^{
// 添加任務(wù)A到group
});
// 添加任務(wù)B到group
dispatch_group_async(group, queue, ^{
// 添加任務(wù)B到group
});
// 當(dāng)任務(wù)A和任務(wù)B都執(zhí)行完后到此來(lái)執(zhí)行任務(wù)C
dispatch_group_notify(group, queue, ^{
// 如果這里還有基于上面兩個(gè)任務(wù)的結(jié)果繼續(xù)執(zhí)行一些代碼,建議還是放到子線程中,等代碼執(zhí)行完畢后在回到主線程
// 回到主線程
dispatch_async(group, dispatch_get_main_queue(), ^{
// 執(zhí)行相關(guān)UI顯示代碼...
});
});
dispatch_set_context與dispatch_set_finalizer_f的配合使用
- 函數(shù)作用:為隊(duì)列設(shè)置任意類型的數(shù)據(jù),并在合適的時(shí)候取出來(lái)用
- 函數(shù)定義
// 設(shè)置context
void dispatch_set_context(dispatch_object_t object, void *context);
// 獲取context
void* dispatch_get_context(dispatch_object_t object);
- 參數(shù)介紹
- 第一個(gè)參數(shù)object:一般指通過(guò)dispatch_queue_create創(chuàng)建的
隊(duì)列 - dispatch_set_context函數(shù)完成了將context
綁定到指定的GCD隊(duì)列上 - dispatch_get_context函數(shù)完成了從指定的GCD隊(duì)列獲取對(duì)應(yīng)的context
- context是一個(gè)
void類型指針,學(xué)過(guò)C語(yǔ)言的朋友應(yīng)該都知道,void類型指針可以指向任意類型,context在這里可以是任意類型的指針
- 第一個(gè)參數(shù)object:一般指通過(guò)dispatch_queue_create創(chuàng)建的
- 補(bǔ)充:
Foundation對(duì)象和Core Foundation對(duì)象間的轉(zhuǎn)換,俗稱橋接,請(qǐng)查看這篇文章 - 完整示例
@interface Data : NSObject
@property(assign, nonatomic) int number;
@end
@implementation Data
// 便于觀察對(duì)象何時(shí)被釋放
- (void)dealloc
{
NSLog(@"Data dealloc...");
}
@end
-----------------------------------------------------------------------------------------
// 定義隊(duì)列的finalizer函數(shù),用于釋放context內(nèi)存
void cleanStaff(void *context) {
// 這里用__bridge轉(zhuǎn)換,不改變內(nèi)存管理權(quán)
Data *data = (__bridge Data *)(context);
NSLog(@"In clean, context number: %d", data.number);
// 釋放context的內(nèi)存!
CFRelease(context);
}
- (void)testBody
{
// 創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("yanhooQueue", DISPATCH_QUEUE_CONCURRENT);
// 創(chuàng)建Data類型context數(shù)據(jù)并初始化
Data *myData = [Data new];
myData.number = 10;
// 綁定context
// 這里用__bridge_retained將OC對(duì)象轉(zhuǎn)換為C對(duì)象,將context的內(nèi)存管理權(quán)從ARC移除,交由我們自己手動(dòng)釋放!
dispatch_set_context(queue, (__bridge_retained void *)(myData));
// 設(shè)置finalizer函數(shù),用于在隊(duì)列執(zhí)行完成后釋放對(duì)應(yīng)context內(nèi)存
dispatch_set_finalizer_f(queue, cleanStaff);
dispatch_async(queue, ^
{
// 獲取隊(duì)列的context數(shù)據(jù)
// 這里用__bridge將C對(duì)象裝換為OC對(duì)象轉(zhuǎn)換,并沒(méi)有改變內(nèi)存管理權(quán)
Data *data = (__bridge Data *)(dispatch_get_context(queue));
// 打印
NSLog(@"1: context number: %d", data.number);
// 修改context保存的數(shù)據(jù)
data.number = 20;
});
}