
上一節(jié)中,我們學習了蘋果官方提供的面向?qū)ο蟮膶崿F(xiàn)多線程的方法——NSThread。這一節(jié)中,我們學習C語言的實現(xiàn)多線程的方法,GCD,這也是我們項目中經(jīng)常使用的一種方法。
NSThread鏈接:詳解多線程(實現(xiàn)篇——NSThread)
多線程概念篇鏈接:詳解多線程(概念篇——進程、線程以及多線程原理)
源碼鏈接:https://github.com/weiman152/Multithreading.git
多線程的實現(xiàn)方法
2.GCD(C語言)
3.NSOperation(OC)
4.C語言的pthread(C語言)
5.其他實現(xiàn)多線程方法
本章主要內(nèi)容提示:
- GCD概念
- 重要概念:任務(wù)(同步、異步)
- 重要概念:隊列(串行、并發(fā))
- GCD的使用
- 案例:圖片下載
- 柵欄函數(shù)
- 延遲執(zhí)行
- 一次性代碼
8_1. 一次性代碼案例:單例- 快速迭代
- 隊列組
10_1. 隊列組案例(下載圖片之后合成圖片)
1.GCD概念
GCD的全程為Grand Central Dispatch,簡單翻譯為大中央調(diào)度。也是蘋果官方開發(fā)的解決多線程的一種方式。
GCD是C語言的,充分利用CPU的多核的并行運算的一種解決多線程的方法。
在GCD中,有兩個重要的概念:任務(wù)和隊列。我們在使用GCD的時候,都是在與這兩個概念打交道,如果這兩個概念不清楚,我們使用的時候就會混淆。下面呢,我們就一起學習這兩個重要的概念吧。
2. 重要概念:任務(wù)(同步、異步)
什么是任務(wù)?
我們上學的時候,老師說,給你布置一項任務(wù)吧,今天負責收取全班同學的作業(yè)。收取作業(yè)這個操作就是任務(wù)。所以,任務(wù)是什么?就是要執(zhí)行的操作。很好理解吧?
什么是同步(sync)?
不開啟新的線程,把任務(wù)添加到當前線程中,在任務(wù)完成之前一直等待。
什么是異步(async)?
可以開啟新的線程,任務(wù)可以添加到新的線程中,不等待隊列完成,可以繼續(xù)執(zhí)行。
舉個例子??:
假如你要做午飯這個任務(wù)。
同步執(zhí)行就是你只有一個鍋,一個爐子,只能先把米飯做熟,然后再去炒菜。
異步執(zhí)行就是你有兩個鍋,兩個爐子,一個鍋里做米飯,一個鍋里炒菜。
注意:雖然異步執(zhí)行具備開啟新線程的能力,但是不一定就要開啟新線程。
3. 重要概念:隊列(串行、并發(fā))
隊列(Dispatch Queue)是什么?
隊列也很好理解的。我們都見過排隊,排隊買飯、排隊取票、排隊上車等等。排隊的時候,總是隊伍前面的先進入,后面的后進入,這就是先進先出(FIFO)。
隊列的示意圖如下:

GCD中的隊列,就是對多個線程進行管理的。
在隊列中,還有兩個概念,串行隊列和并發(fā)隊列。
什么是串行隊列?
我們都吃過糖葫蘆,一串一串的,我們也吃過烤串,也是一串一串的,總是先吃上面的在吃下面的,一個一個的吃。串行隊列也是類似的,一個任務(wù)完成了,再進行下一個任務(wù)。

什么是并發(fā)隊列?
可以開啟多個線程,讓任務(wù)并發(fā)執(zhí)行。但是,并發(fā)隊列只有在異步任務(wù)的時候才會有效。

弄明白了這些概念,我們就開始使用GCD創(chuàng)建多線程吧。
4. GCD的使用
GCD的使用步驟:
1》創(chuàng)建隊列(串行、并發(fā));
2》定制任務(wù)(同步、異步)(想要做的事);
3》將任務(wù)添加到隊列中等待執(zhí)行。
GCD會自動將隊列中的任務(wù)取出來,放到對應(yīng)的線程中執(zhí)行。任務(wù)取出原則還是先進先出。
創(chuàng)建隊列
//1. 創(chuàng)建隊列
/**
// 第一個參數(shù)const char *label : C語言字符串,用來標識
// 第二個參數(shù)dispatch_queue_attr_t attr : 隊列的類型
// 并發(fā)隊列:DISPATCH_QUEUE_CONCURRENT
// 串行隊列:DISPATCH_QUEUE_SERIAL 或者 NULL
dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
*/
//隊列
-(void)test{
//1.1 并發(fā)隊列
//可以開啟多個線程,任務(wù)并發(fā)執(zhí)行
dispatch_queue_t BFqueue = dispatch_queue_create("BFqueue", DISPATCH_QUEUE_CONCURRENT);
//1.2 串行隊列
//任務(wù)一個接一個的執(zhí)行,在一個線程中
dispatch_queue_t CXqueue = dispatch_queue_create("CXqueue", DISPATCH_QUEUE_SERIAL);
//1.3 系統(tǒng)默認提供全局并發(fā)隊列,供使用
/**
系統(tǒng)默認全局隊列 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一個參數(shù):隊列優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
第二個參數(shù): 預(yù)留參數(shù) 0
*/
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//1.4 獲得主隊列
dispatch_queue_t mainQ = dispatch_get_main_queue();
}
創(chuàng)建任務(wù)
//任務(wù)
-(void)test2{
//1. 同步任務(wù):立刻開始執(zhí)行
/**
第一個參數(shù):隊列
第二個參數(shù):要執(zhí)行的操作,是個Block
*/
dispatch_sync(dispatch_get_main_queue(), ^{
//同步執(zhí)行的任務(wù)
});
//2. 異步任務(wù):等主線程執(zhí)行完以后,開啟子線程執(zhí)行任務(wù)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//異步執(zhí)行的任務(wù)
});
}
任務(wù)和隊列的組合
下面我們把任何和隊列進行排列組合,看看每一種的結(jié)果。
1.同步任務(wù)+串行隊列
2.同步任務(wù)+并發(fā)隊列
3.異步任務(wù)+串行隊列
4.異步任務(wù)+并發(fā)隊列
5.同步任務(wù)+主隊列
6.異步任務(wù)+主隊列
7.同步任務(wù)+全局隊列
8.異步任務(wù)+全局隊列
1.同步任務(wù)+串行隊列
//1.同步任務(wù)+串行隊列
- (IBAction)gcdTest1:(id)sender {
//當前線程在主線程
[self test1_1];
/**
總結(jié):同步任務(wù)是不開啟新的線程,把任務(wù)添加到當前線程中,當前線程是主線程,所以是添加到主線程中.串行隊列是任務(wù)一個接一個的執(zhí)行。這里有三個任務(wù),也是先執(zhí)行任務(wù)一,然后執(zhí)行任務(wù)二,最后是任務(wù)三。
打印結(jié)果:
2020-09-29 15:03:58.965539+0800 多線程[3069:121374] 開始測試啦!
2020-09-29 15:03:58.965869+0800 多線程[3069:121374] 1.同步任務(wù)+串行隊列
2020-09-29 15:03:58.966169+0800 多線程[3069:121374] 當前線程:<NSThread: 0x600000478980>{number = 1, name = main}
2020-09-29 15:04:00.967371+0800 多線程[3069:121374] 同步任務(wù)二+串行,睡了2秒
2020-09-29 15:04:00.967774+0800 多線程[3069:121374] 當前線程:<NSThread: 0x600000478980>{number = 1, name = main}
2020-09-29 15:04:00.967987+0800 多線程[3069:121374] 同步任務(wù)三+串行
2020-09-29 15:04:00.968206+0800 多線程[3069:121374] 當前線程:<NSThread: 0x600000478980>{number = 1, name = main}
2020-09-29 15:04:00.968428+0800 多線程[3069:121374] 測試結(jié)束啦!
*/
//我們自己創(chuàng)建個線程試試看
[NSThread detachNewThreadWithBlock:^{
[self test1_1];
}];
//結(jié)果與上次一樣
/**
2020-09-29 15:30:53.381501+0800 多線程[3253:131992] 開始測試啦!
2020-09-29 15:30:53.381831+0800 多線程[3253:131992] 1.同步任務(wù)+串行隊列
2020-09-29 15:30:53.382813+0800 多線程[3253:131992] 當前線程:<NSThread: 0x600003f53680>{number = 7, name = (null)}
2020-09-29 15:30:55.386012+0800 多線程[3253:131992] 同步任務(wù)二+串行,睡了2秒
2020-09-29 15:30:55.386528+0800 多線程[3253:131992] 當前線程:<NSThread: 0x600003f53680>{number = 7, name = (null)}
2020-09-29 15:30:55.386772+0800 多線程[3253:131992] 同步任務(wù)三+串行
2020-09-29 15:30:55.387163+0800 多線程[3253:131992] 當前線程:<NSThread: 0x600003f53680>{number = 7, name = (null)}
2020-09-29 15:30:55.387616+0800 多線程[3253:131992] 測試結(jié)束啦!
*/
}
-(void)test1_1{
NSLog(@"開始測試啦!");
//1.同步任務(wù)+串行隊列
dispatch_queue_t cxQ1 = dispatch_queue_create("串行隊列一", DISPATCH_QUEUE_SERIAL);
dispatch_sync(cxQ1, ^{
NSLog(@"1.同步任務(wù)+串行隊列");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_sync(cxQ1, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"同步任務(wù)二+串行,睡了2秒");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_sync(cxQ1, ^{
NSLog(@"同步任務(wù)三+串行");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束啦!");
}
示意圖:

2.同步任務(wù)+并發(fā)隊列
//2.同步任務(wù)+并發(fā)隊列
- (IBAction)gcdTest2:(id)sender {
NSLog(@"開始測試啦,2.同步任務(wù)+并發(fā)隊列");
dispatch_queue_t bfQ = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(bfQ, ^{
NSLog(@"任務(wù)一");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_sync(bfQ, ^{
[NSThread sleepForTimeInterval:3.0];
NSLog(@"任務(wù)二");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_sync(bfQ, ^{
NSLog(@"任務(wù)三");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束啦");
/**
總結(jié):同步任務(wù),把任務(wù)添加到當前線程,當前在主線程,所以三個任務(wù)都添加到主線程中執(zhí)行。
*/
}
打印結(jié)果:

總結(jié):同步任務(wù),把任務(wù)添加到當前線程,當前在主線程,所以三個任務(wù)都添加到主線程中執(zhí)行。同步任務(wù)不具備開啟新線程的能力,所以沒有新的線程。
示意圖如下:

3.異步任務(wù)+串行隊列
//3.異步任務(wù)+串行隊列
- (IBAction)gcdTest3:(id)sender {
NSLog(@"開始測試,3.異步任務(wù)+串行隊列");
dispatch_queue_t cxq = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
dispatch_async(cxq, ^{
NSLog(@"任務(wù)一");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_async(cxq, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_async(cxq, ^{
NSLog(@"任務(wù)三");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束啦!");
}
打印結(jié)果:

總結(jié):
異步任務(wù)可以開啟新的線程,也不會阻塞當前線程。當前是主線程,因為開啟新的線程需要時間,所以先打印的主線程的兩句話。開啟線程以后,因為是串行隊列,任務(wù)在隊列中一個接一個的執(zhí)行。
示意圖如下:

4.異步任務(wù)+并發(fā)隊列
//4.異步任務(wù)+并發(fā)隊列
- (IBAction)gcdTest4:(id)sender {
NSLog(@"測試開始,異步任務(wù)+并發(fā)隊列");
dispatch_queue_t bfq = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(bfq, ^{
NSLog(@"任務(wù)一");
NSLog(@"1.當前線程:%@",[NSThread currentThread]);
});
dispatch_async(bfq, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二");
NSLog(@"2.當前線程:%@",[NSThread currentThread]);
});
dispatch_async(bfq, ^{
NSLog(@"任務(wù)三");
NSLog(@"3.當前線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束了!");
}
打印結(jié)果:

總結(jié):
異步任務(wù)具備開啟新的線程的能力,此案例中有三個任務(wù),開啟了三個線程。并發(fā)隊列把任務(wù)分配給子線程中執(zhí)行。
示意圖如下:

5.同步任務(wù)+主隊列(死鎖??)
//5.同步任務(wù)+主隊列:死鎖
- (IBAction)gcdTest5:(id)sender {
NSLog(@"測試開始,同步任務(wù)+主隊列");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)一");
NSLog(@"1.線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束");
}
結(jié)果:

為什么嘞?
我們把代碼按照任務(wù)詳細分一下:

網(wǎng)上很多人會把上面代碼分為三個任務(wù),但是我認為,分為四個任務(wù)更能夠理解死鎖是如何產(chǎn)生的,因為即使任務(wù)四不存在,依然會發(fā)生死鎖。
個人分析:
當前在主線程中,任務(wù)一順利執(zhí)行,然后執(zhí)行dispatch_sync這個函數(shù),也就是任務(wù)二,函數(shù)體要執(zhí)行的是任務(wù)三。任務(wù)二要等任務(wù)三執(zhí)行完成才能返回。因為主線程是串行的,所以任務(wù)三是在任務(wù)二后面執(zhí)行的。這就造成了任務(wù)二等待任務(wù)三執(zhí)行完返回,任務(wù)三要等待任務(wù)二返回才能執(zhí)行。它倆就這樣僵持住了,互相等待了,造成了死鎖。
示意圖如下:

6.異步任務(wù)+主隊列
//6.異步任務(wù)+主隊列
- (IBAction)gcdTest6:(id)sender {
NSLog(@"測試開始,異步任務(wù)+主隊列");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)一");
NSLog(@"1.線程:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二");
NSLog(@"2.線程:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)三");
NSLog(@"3.線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束。");
}
打印結(jié)果:

總結(jié):
我們知道,主隊列是串行隊列,異步任務(wù)可以開啟新的線程,但是不一定會開啟新的線程。這里就沒有開啟新的線程,而是把任務(wù)添加到主線程中。異步任務(wù)不會阻塞主線程,所以先打印了主線程中的兩句話,然后順序執(zhí)行三個任務(wù)。
示意圖如下:

7.同步任務(wù)+全局隊列(也是并發(fā)隊列)
//7.同步任務(wù)+全局隊列(也是并發(fā)隊列)
- (IBAction)gcdTest7:(id)sender {
NSLog(@"開始,同步任務(wù)+全局隊列(也是并發(fā)隊列)");
dispatch_queue_t global = dispatch_get_global_queue(0, 0);
dispatch_sync(global, ^{
NSLog(@"任務(wù)一");
NSLog(@"1.線程:%@",[NSThread currentThread]);
});
dispatch_sync(global, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二");
NSLog(@"2.線程:%@",[NSThread currentThread]);
});
dispatch_sync(global, ^{
NSLog(@"任務(wù)三");
NSLog(@"3.線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束了!");
}
結(jié)果:

總結(jié):
同步任務(wù)不開啟新的線程,而是把任務(wù)添加到當前線程,也就是主線程中。全局隊列是個并發(fā)隊列,并發(fā)隊列只有在異步任務(wù)的時候才能起作用,因為當前只有一個線程,并發(fā)是無效果的。因為同步任務(wù)會阻塞當前線程,直到任務(wù)完成。所以會先打印最上面的一行在主線程中的代碼,接著阻塞,執(zhí)行同步任務(wù),也就是任務(wù)一、任務(wù)二和任務(wù)三,直到所有任務(wù)執(zhí)行完了,在繼續(xù)執(zhí)行主線程的最后一行打印。
示意圖如下:

8.異步任務(wù)+全局隊列(也是并發(fā)隊列)
//8.異步任務(wù)+全局隊列(也是并發(fā)隊列)
- (IBAction)gcdTest8:(id)sender {
NSLog(@"開始,異步任務(wù)+全局隊列");
dispatch_queue_t global = dispatch_get_global_queue(0, 0);
dispatch_async(global, ^{
NSLog(@"任務(wù)一");
NSLog(@"1.線程:%@",[NSThread currentThread]);
});
dispatch_async(global, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二");
NSLog(@"2.線程:%@",[NSThread currentThread]);
});
dispatch_async(global, ^{
NSLog(@"任務(wù)三");
NSLog(@"3.線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束了");
}
打印結(jié)果:

總結(jié):
異步任務(wù)是可以開啟新線程的。這里的三個異步任務(wù)開啟了三個線程。全局隊列也是并發(fā)隊列,把任務(wù)分發(fā)給三個子線程中執(zhí)行。主線程不會阻塞,所以直接打印了主線程的內(nèi)容。開啟子線程需要時間。由于任務(wù)二睡了2秒,所以先打印的任務(wù)一和任務(wù)三,最后是任務(wù)二。
示意圖如下:

GCD的任務(wù)和隊列的總結(jié):

簡單一點:

觀察之后發(fā)現(xiàn),
同步任務(wù)都不開啟新的線程,都會阻塞當前線程,大部分都是串行執(zhí)行任務(wù)。
異步任務(wù)都不會阻塞當前線程,大部分會開啟新的線程。
5. 案例:圖片下載
因為下載圖片是耗時操作,一般我們都會放在子線程中進行下載,等圖片下載完成以后,再把圖片放在主線程中顯示。
下面我們就用GCD演示這一過程。
//案例:下載圖片,并顯示
- (IBAction)downLoad:(id)sender {
NSString * url = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1601469152745&di=b68e17d74c30d400e1df9473ded0eb1c&imgtype=0&src=http%3A%2F%2Fhbimg.huabanimg.com%2F959c8754d77244788f5a8f775ee36dec4a5d362e236d9-eP3CI9_fw658";
dispatch_queue_t global = dispatch_get_global_queue(0, 0);
dispatch_async(global, ^{
NSURL * imgUrl = [NSURL URLWithString:url];
NSData * data = [NSData dataWithContentsOfURL:imgUrl];
UIImage *image = [UIImage imageWithData:data];
NSLog(@"下載圖的線程:%@",[NSThread currentThread]);
//回到主線程,給圖片賦值
dispatch_async(dispatch_get_main_queue(), ^{
self.showImg.image = image;
NSLog(@"顯示圖的線程:%@",[NSThread currentThread]);
});
});
}
結(jié)果:


打印結(jié)果:

6. 柵欄函數(shù)
dispatch_barrier_async(queue, ^{
NSLog(@"###############我是個柵欄################");
});
柵欄函數(shù)的作用是,控制任務(wù)的執(zhí)行順序.
我們用代碼試試看,我們使用異步任務(wù)+并發(fā)隊列。
//柵欄函數(shù)
- (IBAction)zhalan:(id)sender {
//柵欄函數(shù),可以控制任務(wù)的執(zhí)行順序
//1.創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
//2.創(chuàng)建任務(wù),異步任務(wù)
dispatch_async(queue, ^{
NSLog(@"任務(wù)一");
[self run:@"1"];
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)二");
[self run:@"2"];
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)三");
[self run:@"3"];
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)四");
[self run:@"4"];
});
}
-(void)run:(NSString *)mark {
for (int i = 0; i<5; i++) {
NSLog(@"任務(wù):%@,i = %d, 線程:%@",mark,i,[NSThread currentThread]);
}
}
打印結(jié)果:

并發(fā)執(zhí)行的線程,任務(wù)的順序是不可控的。
我們使用柵欄函數(shù)后看看。
//柵欄函數(shù)
- (IBAction)zhalan:(id)sender {
//柵欄函數(shù),可以控制任務(wù)的執(zhí)行順序
//1.創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
//2.創(chuàng)建任務(wù),異步任務(wù)
dispatch_async(queue, ^{
NSLog(@"任務(wù)一");
[self run:@"1"];
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)二");
[self run:@"2"];
});
//柵欄函數(shù)
dispatch_barrier_async(queue, ^{
NSLog(@"###############我是個柵欄################");
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)三");
[self run:@"3"];
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)四");
[self run:@"4"];
});
}
-(void)run:(NSString *)mark {
for (int i = 0; i<5; i++) {
NSLog(@"任務(wù):%@,i = %d, 線程:%@",mark,i,[NSThread currentThread]);
}
}


我們發(fā)現(xiàn),使用柵欄函數(shù)之后,柵欄前面的兩個任務(wù)先執(zhí)行完,然后執(zhí)行柵欄函數(shù),最后在執(zhí)行后面的任務(wù)。就像一座柵欄一樣,把異步執(zhí)行的無順序的任務(wù)隔離開了,變成了部分有序的代碼。
7. 延遲執(zhí)行
有的時候,我們需要延遲幾秒后在執(zhí)行某些操作,這個時候,我們就可以使用GCD的dispatch_after來實現(xiàn)啦。
dispatch_after不會阻塞當前線程,可以在主線程,也可以在子線程中執(zhí)行代碼。
//延遲執(zhí)行
- (IBAction)yanchi:(id)sender {
//在主線程中延遲2秒執(zhí)行
NSLog(@"開始啦!");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"哈哈哈哈哈,延遲了嘛");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
NSLog(@"結(jié)束啦");
//子線程中延遲3秒執(zhí)行
NSLog(@"再次開始啦");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
NSLog(@"哎呀呀,3秒哦");
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
NSLog(@"又一次結(jié)束啦!");
}
結(jié)果:

延遲執(zhí)行,我們還有其他的方法,比如:
//延遲執(zhí)行的其他方法
[self performSelector:@selector(YC) withObject:nil afterDelay:2.0];
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(YC) userInfo:nil repeats:NO];
-(void)YC {
NSLog(@"延遲執(zhí)行喲,線程:%@",[NSThread currentThread]);
}
結(jié)果:

也是可以延遲執(zhí)行的。但是呢,這兩個方法都是在主線程中執(zhí)行的。
8. 一次性代碼
dispatch_once
我們在創(chuàng)建單例的時候經(jīng)常使用GCD的dispatch_once這個方法,保證代碼只會執(zhí)行一次。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"此代碼只會執(zhí)行一次。");
});
因為此段代碼常用于單例中,我們創(chuàng)建個單例試試。
8_1. 一次性代碼案例:單例

代碼如下:
GCDTest.h
//
// GCDTest.h
// Multithreading
//
// Created by wenhuanhuan on 2020/10/2.
// Copyright ? 2020 weiman. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface GCDTest : NSObject<NSCopying,NSMutableCopying>
+(instancetype)shareGCDTest;
@end
NS_ASSUME_NONNULL_END
GCDTest.m
//
// GCDTest.m
// Multithreading
//
// Created by wenhuanhuan on 2020/10/2.
// Copyright ? 2020 weiman. All rights reserved.
//
#import "GCDTest.h"
@implementation GCDTest
+(instancetype)shareGCDTest{
return [[self alloc] init];
}
static GCDTest * instance;
+(instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (instance==nil) {
instance = [super allocWithZone:zone];
}
});
return instance;
}
-(id)copyWithZone:(NSZone *)zone{
return instance;
}
-(id)mutableCopyWithZone:(NSZone *)zone{
return instance;
}
@end
測試:
//一次性代碼
- (IBAction)onceAction:(id)sender {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"此代碼只會執(zhí)行一次。");
});
//創(chuàng)建個單例試試
GCDTest * test1 = [[GCDTest alloc] init];
GCDTest * test2 = [GCDTest shareGCDTest];
GCDTest * test3 = [test1 copy];
GCDTest * test4 = [test1 mutableCopy];
NSLog(@"test1:%@",test1);
NSLog(@"test2:%@",test2);
NSLog(@"test3:%@",test3);
NSLog(@"test4:%@",test4);
}
看看結(jié)果:

我們發(fā)現(xiàn),我們創(chuàng)建的四個對象的地址都是一樣的,這就是一個合理的單例啦。
9. 快速迭代
我們需要使用循環(huán)的時候,一般用for循環(huán),for_in循環(huán),while循環(huán),do...while循環(huán),swift中還有更多其他的循環(huán)。在GCD中,也給我們提供了一種快速的循環(huán)方法,dispatch_apply。
/*
第一個參數(shù):迭代的次數(shù)
第二個參數(shù):在哪個隊列中執(zhí)行
第三個參數(shù):block要執(zhí)行的任務(wù)
*/
dispatch_apply(10, queue, ^(size_t index) {
});
為什么它是快速迭代呢?
因為它會開啟多個線程并發(fā)執(zhí)行循環(huán)體內(nèi)的操作。
如果在串行隊列中,dispatch_apply就和for循環(huán)一樣順序執(zhí)行,就沒有意義了。
在并發(fā)隊列中,dispatch_apply會開啟多個線程并發(fā)執(zhí)行,提高效率。
//快速迭代
- (IBAction)kuaisu:(id)sender {
NSLog(@"開始快速迭代");
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"index=%zd, 線程:%@",index, [NSThread currentThread]);
});
}
結(jié)果:

10. 隊列組
隊列組可以在組中的并發(fā)線程都執(zhí)行完成后,進行某些操作。
// 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
// 創(chuàng)建并行隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 執(zhí)行隊列組任務(wù)
dispatch_group_async(group, queue, ^{
});
//隊列組中的任務(wù)執(zhí)行完畢之后,執(zhí)行該函數(shù)
dispatch_group_notify(group, queue, ^{
});
看個簡單例子:
//隊列組
- (IBAction)duilie:(id)sender {
//創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("并發(fā)", DISPATCH_QUEUE_CONCURRENT);
//執(zhí)行隊列組任務(wù)
dispatch_group_async(group, queue, ^{
NSLog(@"這是個隊列組中的任務(wù),編號1");
NSLog(@"任務(wù)一:線程%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"這是個隊列組中的任務(wù),編號2,睡了1秒");
NSLog(@"任務(wù)二:線程%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"這是個隊列組中的任務(wù),編號3");
NSLog(@"任務(wù)三:線程%@",[NSThread currentThread]);
});
//隊列組執(zhí)行完之后執(zhí)行的函數(shù)
dispatch_group_notify(group, queue, ^{
NSLog(@"隊列組任務(wù)執(zhí)行完成。");
});
}
看看打印結(jié)果:

我們在項目中,也是會經(jīng)常遇到這樣的問題的。比如,我們要顯示一個頁面,但是這個頁面有三個接口,我們需要等待三個接口的數(shù)據(jù)都返回來之后再進行顯示。這個時候,就可以把三個網(wǎng)絡(luò)請求放在隊列組中,等待全部完成后,在進行UI顯示。
這里,我們舉一個例子吧。
我們要進行圖片合成的操作,需要下載兩張圖片,等兩張圖片都下載完成了,再把圖片合成一張。
10_1. 隊列組案例(下載圖片之后合成圖片)
UI如下:

代碼如下:
@property (weak, nonatomic) IBOutlet UIImageView *imageOne;
@property (weak, nonatomic) IBOutlet UIImageView *imageTwo;
@property (weak, nonatomic) IBOutlet UIImageView *finalImage;
@property (nonatomic,strong)UIImage * image1;
@property (nonatomic,strong)UIImage * image2;
//隊列組案例:合成圖片
- (IBAction)groupDemo:(id)sender {
//1.創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//2.創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("并發(fā)", DISPATCH_QUEUE_CONCURRENT);
//3.下載圖片一
dispatch_group_async(group, queue, ^{
NSString * str = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1601638749466&di=92ace2ffa924fe6063e7a221729006b1&imgtype=0&src=http%3A%2F%2Fpic.autov.com.cn%2Fimages%2Fcms%2F20119%2F6%2F1315280805177.jpg";
self.image1 = [self loadImage:str];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageOne.image = self.image1;
});
});
//4.下載圖片二
dispatch_group_async(group, queue, ^{
NSString * str = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1601638873771&di=07129fd95c56096a4282d3b072594491&imgtype=0&src=http%3A%2F%2Fimg.51miz.com%2Fpreview%2Felement%2F00%2F01%2F12%2F49%2FE-1124994-5FFE5AC7.jpg";
self.image2 = [self loadImage:str];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageTwo.image = self.image2;
});
});
//5.合成圖片
dispatch_group_notify(group, queue, ^{
//圖形上下文開啟
UIGraphicsBeginImageContext(CGSizeMake(300, 200));
//圖形二
[self.image2 drawInRect:CGRectMake(0, 0, 300, 200)];
//圖形一
[self.image1 drawInRect:CGRectMake(100, 50, 100, 100)];
//獲取新的圖片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
//關(guān)閉上下文
UIGraphicsEndImageContext();
//回到主線程,顯示圖片
dispatch_async(dispatch_get_main_queue(), ^{
self.finalImage.image = image;
NSLog(@"完成圖片的合成");
});
});
}
//下載圖片
-(UIImage *)loadImage:(NSString *)strUrl {
NSLog(@"當前線程:%@",[NSThread currentThread]);
NSURL * url = [NSURL URLWithString:strUrl];
NSData * data = [NSData dataWithContentsOfURL:url];
UIImage * image = [UIImage imageWithData:data];
return image;
}
結(jié)果如下圖:

注意圖片的順序。
到此為止,GCD的基本內(nèi)容我們已經(jīng)學習完了,如有遺漏錯失,還請留言指教,謝謝!
下一節(jié)中,我們將探究NSOperation實現(xiàn)多線程。
祝大家生活愉快!