一.什么是GCD?
1.GCD (Grand Central Dispatch) 是Apple公司開發(fā)的一種技術(shù),它旨在優(yōu)化多核環(huán)境中的并發(fā)操作并取代傳統(tǒng)多線程的編程模式。
2.GCD是基于C語言的線程管理方案,使用者無需過多參與線程的管理,只需要將想要執(zhí)行的代碼,添加到想要添加的調(diào)度隊(duì)列即可。
3.GCD主要用在后臺(tái)執(zhí)行較慢任務(wù);延遲執(zhí)行任務(wù);以及在后臺(tái)任務(wù)中,切換回主線程,更新UI。
二.GCD的優(yōu)勢(shì)
GCD是蘋果公司為多核的并行運(yùn)算提出的解決方案。
GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)。
GCD會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)。
程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼。
三.任務(wù)和隊(duì)列
3.1 隊(duì)列
隊(duì)列:用于存放要執(zhí)行的任務(wù)。隊(duì)列是一種特殊的線性表,采用FIFO(先進(jìn)先出)的原則,即新任務(wù)總是被插入到隊(duì)列的末尾,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開始讀取。每讀取一個(gè)任務(wù),則從隊(duì)列中釋放一個(gè)任務(wù)。在 GCD中有兩種隊(duì)列:
- 串行隊(duì)列(Serial Dispatch Queue)
一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)
- 并發(fā)隊(duì)列(Concurrent Dispatch Queue)
可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開啟多個(gè)線程執(zhí)行任務(wù))并發(fā)功能只有在異步(dispatch_async) 函數(shù)下才有效
DISPATCH_QUEUE_SERIAL: 表示串行,也可以使用NULL表示,因?yàn)楹甓x定義的為NULL。
DISPATCH_QUEUE_CONCURRENT:表示并發(fā)
3.2 任務(wù)
任務(wù):在線程中執(zhí)行的操作,GCD中就是指Block中的代碼。任務(wù)有兩種方式:同步執(zhí)行(sync)和異步執(zhí)行(async)。區(qū)別在于是否會(huì)創(chuàng)建新的線程。同步和異步的主要區(qū)別在于會(huì)不會(huì)阻塞當(dāng)前線程,直到Block中的任務(wù)執(zhí)行完畢。
- 同步執(zhí)行:當(dāng)前任務(wù)不完成,不會(huì)執(zhí)行下個(gè)任務(wù)。
- 異步執(zhí)行:當(dāng)前任務(wù)不完成,不會(huì)等待,同樣可以執(zhí)行下個(gè)任務(wù)。
四.GCD的串行隊(duì)列,并發(fā)隊(duì)列和全局隊(duì)列
各種隊(duì)列的執(zhí)行效果:

4.1并發(fā)隊(duì)列
1.并發(fā)隊(duì)列,同步任務(wù)(不會(huì)開啟新的線程,并發(fā)隊(duì)列失去了并發(fā)的功能。)
//1.創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_CONCURRENT);
//2.添加任務(wù)到隊(duì)列中執(zhí)行
for (int i=0; i<10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
NSLog(@"come here");
總結(jié):不會(huì)開啟線程,并且會(huì)順序執(zhí)行。
2.并發(fā)隊(duì)列,異步任務(wù)(執(zhí)行較慢的任務(wù),例如大量計(jì)算,網(wǎng)絡(luò)請(qǐng)求等。)
//1.創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_CONCURRENT);
//2.添加任務(wù)到隊(duì)列中執(zhí)行
for (int i=0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
NSLog(@"come here");
總結(jié):會(huì)開啟線程,不會(huì)順序執(zhí)行。
4.2串行隊(duì)列
1.串行隊(duì)列,同步任務(wù)(在新線程中執(zhí)行任務(wù),并且等待線程執(zhí)行完畢再向后執(zhí)行,幾乎不用)
//1.創(chuàng)建串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_SERIAL);
//2.添加任務(wù)到隊(duì)列中執(zhí)行
for (int i=0; i<10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread],i);
});
}
2.串行隊(duì)列,異步任務(wù)
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_SERIAL);
for (int i=0; i<10; i++) {
NSLog(@"%d-----",i); //主線程,會(huì)打印完9之后,才會(huì)執(zhí)行come here
dispatch_async(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread],i); //子線程
});
}
NSLog(@"come here"); //在主線程,和隊(duì)列沒有任何關(guān)系
2021-03-23 23:35:59.401760+0800 多線程Demo[32679:1746588] 0-----
2021-03-23 23:35:59.401965+0800 多線程Demo[32679:1746588] 1-----
2021-03-23 23:35:59.402048+0800 多線程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 0
2021-03-23 23:35:59.402079+0800 多線程Demo[32679:1746588] 2-----
2021-03-23 23:35:59.402199+0800 多線程Demo[32679:1746588] 3-----
2021-03-23 23:35:59.402266+0800 多線程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 1
2021-03-23 23:35:59.402307+0800 多線程Demo[32679:1746588] 4-----
2021-03-23 23:35:59.402397+0800 多線程Demo[32679:1746588] 5-----
2021-03-23 23:35:59.402404+0800 多線程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 2
2021-03-23 23:35:59.402680+0800 多線程Demo[32679:1746588] 6-----
2021-03-23 23:35:59.402908+0800 多線程Demo[32679:1746588] 7-----
2021-03-23 23:35:59.403205+0800 多線程Demo[32679:1746588] 8-----
2021-03-23 23:35:59.403466+0800 多線程Demo[32679:1746588] 9-----
2021-03-23 23:35:59.403737+0800 多線程Demo[32679:1746588] come here
2021-03-23 23:35:59.404267+0800 多線程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 3
2021-03-23 23:35:59.404670+0800 多線程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 4
2021-03-23 23:35:59.405344+0800 多線程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 5
2021-03-23 23:35:59.405665+0800 多線程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 6
2021-03-23 23:35:59.406199+0800 多線程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 7
2021-03-23 23:35:59.407880+0800 多線程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 8
2021-03-23 23:35:59.408041+0800 多線程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 9
- 首先肯定是打印完9之后,才會(huì)打印come here,因?yàn)槎际窃谥骶€程,然后打印數(shù)字和打印線程是交叉執(zhí)行的。
總結(jié):會(huì)開啟新的線程,任務(wù)會(huì)順序完成。
4.3主隊(duì)列
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@",[NSThread currentThread]);
});
- 總結(jié):不會(huì)開啟新線程,串行執(zhí)行任務(wù)。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@“在同步主線程中執(zhí)行,慎用,否則會(huì)死鎖”);
});
- 總結(jié):使用sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù),會(huì)卡住當(dāng)前的串行隊(duì)列(產(chǎn)生死鎖)
五.GCD的其他用法以及注意事項(xiàng)
5.1異步主線程(用于在后臺(tái)線程的任務(wù)將要完成時(shí),切換到主線程更新UI)(不會(huì)開新線程)
//主隊(duì)列是專門負(fù)責(zé)在主線程上調(diào)度任務(wù)的隊(duì)列 --> 不會(huì)開線程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"在異步主線程中執(zhí)行");
});
// 執(zhí)?耗時(shí)的異步操作...
dispatch_async( dispatch_get_global_queue(0, 0), ^{ //請(qǐng)求數(shù)據(jù)
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主線程,執(zhí)?UI刷新操作
//對(duì)圖片或別的操作進(jìn)行賦值等,回到主線程
});
});
5.2全局隊(duì)列(相當(dāng)于并發(fā)隊(duì)列)
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@",[NSThread currentThread]);
});
5.3延時(shí)執(zhí)行線程
//延遲
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"我在五秒后打印");
});
5.4單例模式(只執(zhí)行一次)
static GGT_Singleton *singleton = nil; //在.m中保留一個(gè)全局的static的實(shí)例
+ (GGT_Singleton *)sharedSingleton {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[GGT_Singleton alloc]init];
});
return singleton;
}
5.5隊(duì)列組
如果有三個(gè)異步操作(例:網(wǎng)絡(luò)請(qǐng)求ABC),想在三個(gè)請(qǐng)求全部執(zhí)行完返回結(jié)果后,再做其他操作。
1、這三個(gè)網(wǎng)絡(luò)請(qǐng)求,執(zhí)行無序,并發(fā)執(zhí)行。
2、不論返回失敗還是成功都算返回結(jié)果。
如何處理?
如果滿足上面的需求,需要使用GCD里面的dispatch_group_enter和dispatch_group_leave來實(shí)現(xiàn)。
dispatch_group_enter:通知 group,下個(gè)任務(wù)要放入 group 中執(zhí)行了。
dispatch_group_leave:通知 group,任務(wù)成功完成,要移除,與enter()成對(duì)出現(xiàn)。
dispatch_group_notify:只要任務(wù)組完成才會(huì)調(diào)用,不完成不會(huì)調(diào)用。
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
NSLog(@"1 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), queue, ^{
NSLog(@"2 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操作都執(zhí)行完畢后,回到主線程...
NSLog(@"結(jié)束");
});
2022-02-11 18:18:51.877309+0800 TestDemo[14721:238852] 1 <NSThread: 0x6000026d6d00>{number = 3, name = (null)}
2022-02-11 18:18:53.071275+0800 TestDemo[14721:238852] 2 <NSThread: 0x6000026d6d00>{number = 3, name = (null)}
2022-02-11 18:18:53.071532+0800 TestDemo[14721:238763] 結(jié)束
5.6快速迭代(使用dispatch_apply函數(shù)能進(jìn)行快速迭代遍歷)
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){ //%zu用來輸出size_t 類型
// 執(zhí)行10次代碼,index順序不確定
NSLog(@"%zu",index);
});
5.7 iOS中的多讀單寫安全方案
//初始化隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("rw_queue",DISPATCH_QUEUE_CONCURRENT);
//讀
dispatch_async(queue, ^{
});
//寫
dispatch_barrier_async(queue, ^{
});
六.小結(jié)
- 同步任務(wù)死鎖:當(dāng)前是在主線程,讓主隊(duì)列執(zhí)行同步任務(wù)!
//主隊(duì)列是專門負(fù)責(zé)在主線程上調(diào)度任務(wù)的隊(duì)列 --> 不會(huì)開線程
//1.隊(duì)列 --> 已啟動(dòng)主線程,就可以獲取主隊(duì)列
dispatch_queue_t q = dispatch_get_main_queue();
//2.同步任務(wù) ==> 死鎖
dispatch_sync(q, ^{
NSLog(@"能來嗎? ");
});
NSLog(@"come here");
解決辦法:
void (^task)() = ^{
NSLog(@"這里%@",[NSThread currentThread]);
//1.隊(duì)列 --> 已啟動(dòng)主線程,就可以獲取主隊(duì)列
dispatch_queue_t q = dispatch_get_main_queue();
//2.同步任務(wù)
dispatch_sync(q, ^{
NSLog(@"能來嗎? %@",[NSThread currentThread]);
});
NSLog(@"come here");
};
dispatch_async(dispatch_get_global_queue(0, 0), task);
競爭&同步:兩個(gè)線程搶奪同一個(gè)資源,就會(huì)競爭,為了防止競爭,一個(gè)線程擁有資源的時(shí)候,會(huì)對(duì)資源加鎖,另一個(gè)線程就要等待解鎖以后再擁有這個(gè)資源,這叫同步。
死鎖:兩個(gè)線程互相等待對(duì)方釋放資源。
主線程&后臺(tái)線程:主線程也叫前臺(tái)線程,程序啟動(dòng)的默認(rèn)線程,操作UI的線程。后臺(tái)線程,即非主線程,用于不影響主線程的完成一些任務(wù)。
同步&異步:同步執(zhí)行線程,等待新線程執(zhí)行完以后,再繼續(xù)執(zhí)行當(dāng)前線程,很少用到。異步執(zhí)行線程,在執(zhí)行新線程的同時(shí),繼續(xù)執(zhí)行當(dāng)前線程,常用。
iOS 多線程入門01--概念知識(shí)
iOS 多線程入門02--NSThread
iOS 多線程入門04--NSOperation