上一節(jié)中,我們學(xué)習(xí)了蘋果官方提供的面向?qū)ο蟮膶?shí)現(xiàn)多線程的方法——NSThread。這一節(jié)中坪蚁,我們學(xué)習(xí)C語言的實(shí)現(xiàn)多線程的方法,GCD镜沽,這也是我們項(xiàng)目中經(jīng)常使用的一種方法敏晤。
NSThread鏈接:詳解多線程(實(shí)現(xiàn)篇——NSThread)
多線程概念篇鏈接:詳解多線程(概念篇——進(jìn)程、線程以及多線程原理)
源碼鏈接:https://github.com/weiman152/Multithreading.git
多線程的實(shí)現(xiàn)方法
2.GCD(C語言)
3.NSOperation(OC)
4.C語言的pthread(C語言)
5.其他實(shí)現(xiàn)多線程方法
本章主要內(nèi)容提示:
- GCD概念
- 重要概念:任務(wù)(同步缅茉、異步)
- 重要概念:隊(duì)列(串行茵典、并發(fā))
- GCD的使用
- 案例:圖片下載
- 柵欄函數(shù)
- 延遲執(zhí)行
- 一次性代碼
8_1. 一次性代碼案例:單例- 快速迭代
- 隊(duì)列組
10_1. 隊(duì)列組案例(下載圖片之后合成圖片)
1.GCD概念
GCD的全程為Grand Central Dispatch,簡單翻譯為大中央調(diào)度宾舅。也是蘋果官方開發(fā)的解決多線程的一種方式统阿。
GCD是C語言的,充分利用CPU的多核的并行運(yùn)算的一種解決多線程的方法筹我。
在GCD中扶平,有兩個(gè)重要的概念:任務(wù)和隊(duì)列。我們在使用GCD的時(shí)候蔬蕊,都是在與這兩個(gè)概念打交道结澄,如果這兩個(gè)概念不清楚,我們使用的時(shí)候就會(huì)混淆。下面呢麻献,我們就一起學(xué)習(xí)這兩個(gè)重要的概念吧们妥。
2. 重要概念:任務(wù)(同步、異步)
什么是任務(wù)勉吻?
我們上學(xué)的時(shí)候监婶,老師說,給你布置一項(xiàng)任務(wù)吧齿桃,今天負(fù)責(zé)收取全班同學(xué)的作業(yè)惑惶。收取作業(yè)這個(gè)操作就是任務(wù)。所以短纵,任務(wù)是什么带污?就是要執(zhí)行的操作。很好理解吧香到?
什么是同步(sync)鱼冀?
不開啟新的線程,把任務(wù)添加到當(dāng)前線程中悠就,在任務(wù)完成之前一直等待雷绢。
什么是異步(async)?
可以開啟新的線程理卑,任務(wù)可以添加到新的線程中翘紊,不等待隊(duì)列完成,可以繼續(xù)執(zhí)行藐唠。
舉個(gè)例子??:
假如你要做午飯這個(gè)任務(wù)帆疟。
同步執(zhí)行就是你只有一個(gè)鍋,一個(gè)爐子宇立,只能先把米飯做熟踪宠,然后再去炒菜。
異步執(zhí)行就是你有兩個(gè)鍋妈嘹,兩個(gè)爐子柳琢,一個(gè)鍋里做米飯,一個(gè)鍋里炒菜润脸。
注意:雖然異步執(zhí)行具備開啟新線程的能力柬脸,但是不一定就要開啟新線程。
3. 重要概念:隊(duì)列(串行毙驯、并發(fā))
隊(duì)列(Dispatch Queue)是什么倒堕?
隊(duì)列也很好理解的。我們都見過排隊(duì)爆价,排隊(duì)買飯垦巴、排隊(duì)取票媳搪、排隊(duì)上車等等。排隊(duì)的時(shí)候骤宣,總是隊(duì)伍前面的先進(jìn)入秦爆,后面的后進(jìn)入,這就是先進(jìn)先出(FIFO)憔披。
隊(duì)列的示意圖如下:
GCD中的隊(duì)列等限,就是對多個(gè)線程進(jìn)行管理的。
在隊(duì)列中活逆,還有兩個(gè)概念,串行隊(duì)列和并發(fā)隊(duì)列拗胜。
什么是串行隊(duì)列蔗候?
我們都吃過糖葫蘆,一串一串的埂软,我們也吃過烤串锈遥,也是一串一串的,總是先吃上面的在吃下面的勘畔,一個(gè)一個(gè)的吃所灸。串行隊(duì)列也是類似的,一個(gè)任務(wù)完成了炫七,再進(jìn)行下一個(gè)任務(wù)爬立。
什么是并發(fā)隊(duì)列?
可以開啟多個(gè)線程万哪,讓任務(wù)并發(fā)執(zhí)行侠驯。但是,并發(fā)隊(duì)列只有在異步任務(wù)的時(shí)候才會(huì)有效奕巍。
弄明白了這些概念吟策,我們就開始使用GCD創(chuàng)建多線程吧。
4. GCD的使用
GCD的使用步驟:
1》創(chuàng)建隊(duì)列(串行的止、并發(fā))檩坚;
2》定制任務(wù)(同步、異步)(想要做的事)诅福;
3》將任務(wù)添加到隊(duì)列中等待執(zhí)行匾委。
GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出來,放到對應(yīng)的線程中執(zhí)行氓润。任務(wù)取出原則還是先進(jìn)先出剩檀。
創(chuàng)建隊(duì)列
//1. 創(chuàng)建隊(duì)列
/**
// 第一個(gè)參數(shù)const char *label : C語言字符串,用來標(biāo)識
// 第二個(gè)參數(shù)dispatch_queue_attr_t attr : 隊(duì)列的類型
// 并發(fā)隊(duì)列:DISPATCH_QUEUE_CONCURRENT
// 串行隊(duì)列:DISPATCH_QUEUE_SERIAL 或者 NULL
dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
*/
//隊(duì)列
-(void)test{
//1.1 并發(fā)隊(duì)列
//可以開啟多個(gè)線程旺芽,任務(wù)并發(fā)執(zhí)行
dispatch_queue_t BFqueue = dispatch_queue_create("BFqueue", DISPATCH_QUEUE_CONCURRENT);
//1.2 串行隊(duì)列
//任務(wù)一個(gè)接一個(gè)的執(zhí)行,在一個(gè)線程中
dispatch_queue_t CXqueue = dispatch_queue_create("CXqueue", DISPATCH_QUEUE_SERIAL);
//1.3 系統(tǒng)默認(rèn)提供全局并發(fā)隊(duì)列沪猴,供使用
/**
系統(tǒng)默認(rèn)全局隊(duì)列 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一個(gè)參數(shù):隊(duì)列優(yōu)先級
#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)
第二個(gè)參數(shù): 預(yù)留參數(shù) 0
*/
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//1.4 獲得主隊(duì)列
dispatch_queue_t mainQ = dispatch_get_main_queue();
}
創(chuàng)建任務(wù)
//任務(wù)
-(void)test2{
//1. 同步任務(wù):立刻開始執(zhí)行
/**
第一個(gè)參數(shù):隊(duì)列
第二個(gè)參數(shù):要執(zhí)行的操作辐啄,是個(gè)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ù)和隊(duì)列的組合
下面我們把任何和隊(duì)列進(jìn)行排列組合运嗜,看看每一種的結(jié)果壶辜。
1.同步任務(wù)+串行隊(duì)列
2.同步任務(wù)+并發(fā)隊(duì)列
3.異步任務(wù)+串行隊(duì)列
4.異步任務(wù)+并發(fā)隊(duì)列
5.同步任務(wù)+主隊(duì)列
6.異步任務(wù)+主隊(duì)列
7.同步任務(wù)+全局隊(duì)列
8.異步任務(wù)+全局隊(duì)列
1.同步任務(wù)+串行隊(duì)列
//1.同步任務(wù)+串行隊(duì)列
- (IBAction)gcdTest1:(id)sender {
//當(dāng)前線程在主線程
[self test1_1];
/**
總結(jié):同步任務(wù)是不開啟新的線程,把任務(wù)添加到當(dāng)前線程中担租,當(dāng)前線程是主線程砸民,所以是添加到主線程中.串行隊(duì)列是任務(wù)一個(gè)接一個(gè)的執(zhí)行。這里有三個(gè)任務(wù)奋救,也是先執(zhí)行任務(wù)一岭参,然后執(zhí)行任務(wù)二,最后是任務(wù)三尝艘。
打印結(jié)果:
2020-09-29 15:03:58.965539+0800 多線程[3069:121374] 開始測試?yán)玻? 2020-09-29 15:03:58.965869+0800 多線程[3069:121374] 1.同步任務(wù)+串行隊(duì)列
2020-09-29 15:03:58.966169+0800 多線程[3069:121374] 當(dāng)前線程:<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] 當(dāng)前線程:<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] 當(dāng)前線程:<NSThread: 0x600000478980>{number = 1, name = main}
2020-09-29 15:04:00.968428+0800 多線程[3069:121374] 測試結(jié)束啦!
*/
//我們自己創(chuàng)建個(gè)線程試試看
[NSThread detachNewThreadWithBlock:^{
[self test1_1];
}];
//結(jié)果與上次一樣
/**
2020-09-29 15:30:53.381501+0800 多線程[3253:131992] 開始測試?yán)玻? 2020-09-29 15:30:53.381831+0800 多線程[3253:131992] 1.同步任務(wù)+串行隊(duì)列
2020-09-29 15:30:53.382813+0800 多線程[3253:131992] 當(dāng)前線程:<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] 當(dāng)前線程:<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] 當(dāng)前線程:<NSThread: 0x600003f53680>{number = 7, name = (null)}
2020-09-29 15:30:55.387616+0800 多線程[3253:131992] 測試結(jié)束啦秒际!
*/
}
-(void)test1_1{
NSLog(@"開始測試?yán)玻?);
//1.同步任務(wù)+串行隊(duì)列
dispatch_queue_t cxQ1 = dispatch_queue_create("串行隊(duì)列一", DISPATCH_QUEUE_SERIAL);
dispatch_sync(cxQ1, ^{
NSLog(@"1.同步任務(wù)+串行隊(duì)列");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(cxQ1, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"同步任務(wù)二+串行,睡了2秒");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(cxQ1, ^{
NSLog(@"同步任務(wù)三+串行");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束啦狡汉!");
}
示意圖:
2.同步任務(wù)+并發(fā)隊(duì)列
//2.同步任務(wù)+并發(fā)隊(duì)列
- (IBAction)gcdTest2:(id)sender {
NSLog(@"開始測試?yán)玻?.同步任務(wù)+并發(fā)隊(duì)列");
dispatch_queue_t bfQ = dispatch_queue_create("并發(fā)隊(duì)列", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(bfQ, ^{
NSLog(@"任務(wù)一");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(bfQ, ^{
[NSThread sleepForTimeInterval:3.0];
NSLog(@"任務(wù)二");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_sync(bfQ, ^{
NSLog(@"任務(wù)三");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束啦");
/**
總結(jié):同步任務(wù)娄徊,把任務(wù)添加到當(dāng)前線程,當(dāng)前在主線程盾戴,所以三個(gè)任務(wù)都添加到主線程中執(zhí)行寄锐。
*/
}
打印結(jié)果:
總結(jié):同步任務(wù),把任務(wù)添加到當(dāng)前線程尖啡,當(dāng)前在主線程锐峭,所以三個(gè)任務(wù)都添加到主線程中執(zhí)行。同步任務(wù)不具備開啟新線程的能力可婶,所以沒有新的線程沿癞。
示意圖如下:
3.異步任務(wù)+串行隊(duì)列
//3.異步任務(wù)+串行隊(duì)列
- (IBAction)gcdTest3:(id)sender {
NSLog(@"開始測試,3.異步任務(wù)+串行隊(duì)列");
dispatch_queue_t cxq = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
dispatch_async(cxq, ^{
NSLog(@"任務(wù)一");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(cxq, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(cxq, ^{
NSLog(@"任務(wù)三");
NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束啦矛渴!");
}
打印結(jié)果:
總結(jié):
異步任務(wù)可以開啟新的線程椎扬,也不會(huì)阻塞當(dāng)前線程。當(dāng)前是主線程具温,因?yàn)殚_啟新的線程需要時(shí)間蚕涤,所以先打印的主線程的兩句話。開啟線程以后铣猩,因?yàn)槭谴嘘?duì)列揖铜,任務(wù)在隊(duì)列中一個(gè)接一個(gè)的執(zhí)行。
示意圖如下:
4.異步任務(wù)+并發(fā)隊(duì)列
//4.異步任務(wù)+并發(fā)隊(duì)列
- (IBAction)gcdTest4:(id)sender {
NSLog(@"測試開始达皿,異步任務(wù)+并發(fā)隊(duì)列");
dispatch_queue_t bfq = dispatch_queue_create("并發(fā)隊(duì)列", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(bfq, ^{
NSLog(@"任務(wù)一");
NSLog(@"1.當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(bfq, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任務(wù)二");
NSLog(@"2.當(dāng)前線程:%@",[NSThread currentThread]);
});
dispatch_async(bfq, ^{
NSLog(@"任務(wù)三");
NSLog(@"3.當(dāng)前線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束了天吓!");
}
打印結(jié)果:
總結(jié):
異步任務(wù)具備開啟新的線程的能力贿肩,此案例中有三個(gè)任務(wù),開啟了三個(gè)線程龄寞。并發(fā)隊(duì)列把任務(wù)分配給子線程中執(zhí)行汰规。
示意圖如下:
5.同步任務(wù)+主隊(duì)列(死鎖??)
//5.同步任務(wù)+主隊(duì)列:死鎖
- (IBAction)gcdTest5:(id)sender {
NSLog(@"測試開始,同步任務(wù)+主隊(duì)列");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)一");
NSLog(@"1.線程:%@",[NSThread currentThread]);
});
NSLog(@"測試結(jié)束");
}
結(jié)果:
為什么嘞物邑?
我們把代碼按照任務(wù)詳細(xì)分一下:
網(wǎng)上很多人會(huì)把上面代碼分為三個(gè)任務(wù)溜哮,但是我認(rèn)為,分為四個(gè)任務(wù)更能夠理解死鎖是如何產(chǎn)生的色解,因?yàn)榧词谷蝿?wù)四不存在茂嗓,依然會(huì)發(fā)生死鎖。
個(gè)人分析:
當(dāng)前在主線程中科阎,任務(wù)一順利執(zhí)行述吸,然后執(zhí)行dispatch_sync這個(gè)函數(shù),也就是任務(wù)二萧恕,函數(shù)體要執(zhí)行的是任務(wù)三刚梭。任務(wù)二要等任務(wù)三執(zhí)行完成才能返回肠阱。因?yàn)橹骶€程是串行的票唆,所以任務(wù)三是在任務(wù)二后面執(zhí)行的。這就造成了任務(wù)二等待任務(wù)三執(zhí)行完返回屹徘,任務(wù)三要等待任務(wù)二返回才能執(zhí)行走趋。它倆就這樣僵持住了,互相等待了噪伊,造成了死鎖簿煌。
示意圖如下:
6.異步任務(wù)+主隊(duì)列
//6.異步任務(wù)+主隊(duì)列
- (IBAction)gcdTest6:(id)sender {
NSLog(@"測試開始,異步任務(wù)+主隊(duì)列");
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é):
我們知道姨伟,主隊(duì)列是串行隊(duì)列,異步任務(wù)可以開啟新的線程豆励,但是不一定會(huì)開啟新的線程夺荒。這里就沒有開啟新的線程,而是把任務(wù)添加到主線程中良蒸。異步任務(wù)不會(huì)阻塞主線程技扼,所以先打印了主線程中的兩句話,然后順序執(zhí)行三個(gè)任務(wù)嫩痰。
示意圖如下:
7.同步任務(wù)+全局隊(duì)列(也是并發(fā)隊(duì)列)
//7.同步任務(wù)+全局隊(duì)列(也是并發(fā)隊(duì)列)
- (IBAction)gcdTest7:(id)sender {
NSLog(@"開始剿吻,同步任務(wù)+全局隊(duì)列(也是并發(fā)隊(duì)列)");
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ù)添加到當(dāng)前線程丽旅,也就是主線程中椰棘。全局隊(duì)列是個(gè)并發(fā)隊(duì)列,并發(fā)隊(duì)列只有在異步任務(wù)的時(shí)候才能起作用魔招,因?yàn)楫?dāng)前只有一個(gè)線程晰搀,并發(fā)是無效果的。因?yàn)橥饺蝿?wù)會(huì)阻塞當(dāng)前線程办斑,直到任務(wù)完成外恕。所以會(huì)先打印最上面的一行在主線程中的代碼,接著阻塞乡翅,執(zhí)行同步任務(wù)鳞疲,也就是任務(wù)一、任務(wù)二和任務(wù)三蠕蚜,直到所有任務(wù)執(zhí)行完了尚洽,在繼續(xù)執(zhí)行主線程的最后一行打印。
示意圖如下:
8.異步任務(wù)+全局隊(duì)列(也是并發(fā)隊(duì)列)
//8.異步任務(wù)+全局隊(duì)列(也是并發(fā)隊(duì)列)
- (IBAction)gcdTest8:(id)sender {
NSLog(@"開始靶累,異步任務(wù)+全局隊(duì)列");
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ù)是可以開啟新線程的腺毫。這里的三個(gè)異步任務(wù)開啟了三個(gè)線程。全局隊(duì)列也是并發(fā)隊(duì)列挣柬,把任務(wù)分發(fā)給三個(gè)子線程中執(zhí)行潮酒。主線程不會(huì)阻塞,所以直接打印了主線程的內(nèi)容邪蛔。開啟子線程需要時(shí)間急黎。由于任務(wù)二睡了2秒,所以先打印的任務(wù)一和任務(wù)三侧到,最后是任務(wù)二勃教。
示意圖如下:
GCD的任務(wù)和隊(duì)列的總結(jié):
簡單一點(diǎn):
觀察之后發(fā)現(xiàn),
同步任務(wù)都不開啟新的線程匠抗,都會(huì)阻塞當(dāng)前線程故源,大部分都是串行執(zhí)行任務(wù)。
異步任務(wù)都不會(huì)阻塞當(dāng)前線程汞贸,大部分會(huì)開啟新的線程绳军。
5. 案例:圖片下載
因?yàn)橄螺d圖片是耗時(shí)操作,一般我們都會(huì)放在子線程中進(jìn)行下載著蛙,等圖片下載完成以后删铃,再把圖片放在主線程中顯示。
下面我們就用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(@"###############我是個(gè)柵欄################");
});
柵欄函數(shù)的作用是,控制任務(wù)的執(zhí)行順序.
我們用代碼試試看诫隅,我們使用異步任務(wù)+并發(fā)隊(duì)列腐魂。
//柵欄函數(shù)
- (IBAction)zhalan:(id)sender {
//柵欄函數(shù),可以控制任務(wù)的執(zhí)行順序
//1.創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊(duì)列", 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ā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊(duì)列", 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(@"###############我是個(gè)柵欄################");
});
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ù)之后育叁,柵欄前面的兩個(gè)任務(wù)先執(zhí)行完,然后執(zhí)行柵欄函數(shù)芍殖,最后在執(zhí)行后面的任務(wù)豪嗽。就像一座柵欄一樣,把異步執(zhí)行的無順序的任務(wù)隔離開了豌骏,變成了部分有序的代碼龟梦。
7. 延遲執(zhí)行
有的時(shí)候,我們需要延遲幾秒后在執(zhí)行某些操作窃躲,這個(gè)時(shí)候计贰,我們就可以使用GCD的dispatch_after來實(shí)現(xiàn)啦。
dispatch_after不會(huì)阻塞當(dāng)前線程框舔,可以在主線程蹦玫,也可以在子線程中執(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(@"當(dāng)前線程:%@",[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(@"當(dāng)前線程:%@",[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í)行的。但是呢完丽,這兩個(gè)方法都是在主線程中執(zhí)行的恋技。
8. 一次性代碼
dispatch_once
我們在創(chuàng)建單例的時(shí)候經(jīng)常使用GCD的dispatch_once這個(gè)方法,保證代碼只會(huì)執(zhí)行一次逻族。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"此代碼只會(huì)執(zhí)行一次蜻底。");
});
因?yàn)榇硕未a常用于單例中,我們創(chuàng)建個(gè)單例試試聘鳞。
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(@"此代碼只會(huì)執(zhí)行一次薄辅。");
});
//創(chuàng)建個(gè)單例試試
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)建的四個(gè)對象的地址都是一樣的,這就是一個(gè)合理的單例啦站楚。
9. 快速迭代
我們需要使用循環(huán)的時(shí)候脱惰,一般用for循環(huán),for_in循環(huán)窿春,while循環(huán)拉一,do...while循環(huán),swift中還有更多其他的循環(huán)旧乞。在GCD中舅踪,也給我們提供了一種快速的循環(huán)方法,dispatch_apply良蛮。
/*
第一個(gè)參數(shù):迭代的次數(shù)
第二個(gè)參數(shù):在哪個(gè)隊(duì)列中執(zhí)行
第三個(gè)參數(shù):block要執(zhí)行的任務(wù)
*/
dispatch_apply(10, queue, ^(size_t index) {
});
為什么它是快速迭代呢抽碌?
因?yàn)樗鼤?huì)開啟多個(gè)線程并發(fā)執(zhí)行循環(huán)體內(nèi)的操作。
如果在串行隊(duì)列中决瞳,dispatch_apply就和for循環(huán)一樣順序執(zhí)行货徙,就沒有意義了。
在并發(fā)隊(duì)列中皮胡,dispatch_apply會(huì)開啟多個(gè)線程并發(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. 隊(duì)列組
隊(duì)列組可以在組中的并發(fā)線程都執(zhí)行完成后屡贺,進(jìn)行某些操作蠢棱。
// 創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
// 創(chuàng)建并行隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 執(zhí)行隊(duì)列組任務(wù)
dispatch_group_async(group, queue, ^{
});
//隊(duì)列組中的任務(wù)執(zhí)行完畢之后,執(zhí)行該函數(shù)
dispatch_group_notify(group, queue, ^{
});
看個(gè)簡單例子:
//隊(duì)列組
- (IBAction)duilie:(id)sender {
//創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("并發(fā)", DISPATCH_QUEUE_CONCURRENT);
//執(zhí)行隊(duì)列組任務(wù)
dispatch_group_async(group, queue, ^{
NSLog(@"這是個(gè)隊(duì)列組中的任務(wù),編號1");
NSLog(@"任務(wù)一:線程%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"這是個(gè)隊(duì)列組中的任務(wù),編號2,睡了1秒");
NSLog(@"任務(wù)二:線程%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"這是個(gè)隊(duì)列組中的任務(wù),編號3");
NSLog(@"任務(wù)三:線程%@",[NSThread currentThread]);
});
//隊(duì)列組執(zhí)行完之后執(zhí)行的函數(shù)
dispatch_group_notify(group, queue, ^{
NSLog(@"隊(duì)列組任務(wù)執(zhí)行完成甩栈。");
});
}
看看打印結(jié)果:
我們在項(xiàng)目中泻仙,也是會(huì)經(jīng)常遇到這樣的問題的。比如量没,我們要顯示一個(gè)頁面玉转,但是這個(gè)頁面有三個(gè)接口,我們需要等待三個(gè)接口的數(shù)據(jù)都返回來之后再進(jìn)行顯示殴蹄。這個(gè)時(shí)候究抓,就可以把三個(gè)網(wǎng)絡(luò)請求放在隊(duì)列組中,等待全部完成后袭灯,在進(jìn)行UI顯示刺下。
這里,我們舉一個(gè)例子吧稽荧。
我們要進(jìn)行圖片合成的操作橘茉,需要下載兩張圖片,等兩張圖片都下載完成了,再把圖片合成一張捺癞。
10_1. 隊(duì)列組案例(下載圖片之后合成圖片)
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;
//隊(duì)列組案例:合成圖片
- (IBAction)groupDemo:(id)sender {
//1.創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//2.創(chuàng)建并發(fā)隊(duì)列
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(@"當(dāng)前線程:%@",[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)學(xué)習(xí)完了惕鼓,如有遺漏錯(cuò)失,還請留言指教唐础,謝謝箱歧!
下一節(jié)中,我們將探究NSOperation實(shí)現(xiàn)多線程一膨。
祝大家生活愉快呀邢!