上一节中,我们学习了苹果官方提供的面向对象的实现多线程的方法——NSThread。这一节中,我们学习C语言的实现多线程的方法,GCD,这也是我们项目中经常使用的一种方法。
NSThread链接:详解多线程(实现篇——NSThread)
多线程概念篇链接:详解多线程(概念篇——进程、线程以及多线程原理)
源码链接:https://github.com/weiman152/Multithreading.git
多线程的实现方法
1.NSThread(OC)
2.GCD(C语言)
3.NSOperation(OC)
4.C语言的pthread(C语言)
5.其他实现多线程方法
本章主要内容提示:
- GCD概念
- 重要概念:任务(同步、异步)
- 重要概念:队列(串行、并发)
- GCD的使用
- 案例:图片下载
- 栅栏函数
- 延迟执行
- 一次性代码
8_1. 一次性代码案例:单例- 快速迭代
- 队列组
10_1. 队列组案例(下载图片之后合成图片)
1.GCD概念
GCD的全程为Grand Central Dispatch,简单翻译为大中央调度。也是苹果官方开发的解决多线程的一种方式。
GCD是C语言的,充分利用CPU的多核的并行运算的一种解决多线程的方法。
在GCD中,有两个重要的概念:任务和队列。我们在使用GCD的时候,都是在与这两个概念打交道,如果这两个概念不清楚,我们使用的时候就会混淆。下面呢,我们就一起学习这两个重要的概念吧。
2. 重要概念:任务(同步、异步)
什么是任务?
我们上学的时候,老师说,给你布置一项任务吧,今天负责收取全班同学的作业。收取作业这个操作就是任务。所以,任务是什么?就是要执行的操作。很好理解吧?
什么是同步(sync)?
不开启新的线程,把任务添加到当前线程中,在任务完成之前一直等待。
什么是异步(async)?
可以开启新的线程,任务可以添加到新的线程中,不等待队列完成,可以继续执行。
举个例子:
假如你要做午饭这个任务。
同步执行就是你只有一个锅,一个炉子,只能先把米饭做熟,然后再去炒菜。
异步执行就是你有两个锅,两个炉子,一个锅里做米饭,一个锅里炒菜。
注意:虽然异步执行具备开启新线程的能力,但是不一定就要开启新线程。
3. 重要概念:队列(串行、并发)
队列(Dispatch Queue)是什么?
队列也很好理解的。我们都见过排队,排队买饭、排队取票、排队上车等等。排队的时候,总是队伍前面的先进入,后面的后进入,这就是先进先出(FIFO)。
队列的示意图如下:
GCD中的队列,就是对多个线程进行管理的。
在队列中,还有两个概念,串行队列和并发队列。
什么是串行队列?
我们都吃过糖葫芦,一串一串的,我们也吃过烤串,也是一串一串的,总是先吃上面的在吃下面的,一个一个的吃。串行队列也是类似的,一个任务完成了,再进行下一个任务。
什么是并发队列?
可以开启多个线程,让任务并发执行。但是,并发队列只有在异步任务的时候才会有效。
弄明白了这些概念,我们就开始使用GCD创建多线程吧。
4. GCD的使用
GCD的使用步骤:
1》创建队列(串行、并发);
2》定制任务(同步、异步)(想要做的事);
3》将任务添加到队列中等待执行。
GCD会自动将队列中的任务取出来,放到对应的线程中执行。任务取出原则还是先进先出。
创建队列
//1. 创建队列
/**
// 第一个参数const char *label : C语言字符串,用来标识
// 第二个参数dispatch_queue_attr_t attr : 队列的类型
// 并发队列: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 并发队列
//可以开启多个线程,任务并发执行
dispatch_queue_t BFqueue = dispatch_queue_create("BFqueue", DISPATCH_QUEUE_CONCURRENT);
//1.2 串行队列
//任务一个接一个的执行,在一个线程中
dispatch_queue_t CXqueue = dispatch_queue_create("CXqueue", DISPATCH_QUEUE_SERIAL);
//1.3 系统默认提供全局并发队列,供使用
/**
系统默认全局队列 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一个参数:队列优先级
#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 // 后台
第二个参数: 预留参数 0
*/
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//1.4 获得主队列
dispatch_queue_t mainQ = dispatch_get_main_queue();
}
创建任务
//任务
-(void)test2{
//1. 同步任务:立刻开始执行
/**
第一个参数:队列
第二个参数:要执行的操作,是个Block
*/
dispatch_sync(dispatch_get_main_queue(), ^{
//同步执行的任务
});
//2. 异步任务:等主线程执行完以后,开启子线程执行任务
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//异步执行的任务
});
}
任务和队列的组合
下面我们把任何和队列进行排列组合,看看每一种的结果。
1.同步任务+串行队列
2.同步任务+并发队列
3.异步任务+串行队列
4.异步任务+并发队列
5.同步任务+主队列
6.异步任务+主队列
7.同步任务+全局队列
8.异步任务+全局队列
1.同步任务+串行队列
//1.同步任务+串行队列
- (IBAction)gcdTest1:(id)sender {
//当前线程在主线程
[self test1_1];
/**
总结:同步任务是不开启新的线程,把任务添加到当前线程中,当前线程是主线程,所以是添加到主线程中.串行队列是任务一个接一个的执行。这里有三个任务,也是先执行任务一,然后执行任务二,最后是任务三。
打印结果:
2020-09-29 15:03:58.965539+0800 多线程[3069:121374] 开始测试啦!
2020-09-29 15:03:58.965869+0800 多线程[3069:121374] 1.同步任务+串行队列
2020-09-29 15:03:58.966169+0800 多线程[3069:121374] 当前线程:{number = 1, name = main}
2020-09-29 15:04:00.967371+0800 多线程[3069:121374] 同步任务二+串行,睡了2秒
2020-09-29 15:04:00.967774+0800 多线程[3069:121374] 当前线程:{number = 1, name = main}
2020-09-29 15:04:00.967987+0800 多线程[3069:121374] 同步任务三+串行
2020-09-29 15:04:00.968206+0800 多线程[3069:121374] 当前线程:{number = 1, name = main}
2020-09-29 15:04:00.968428+0800 多线程[3069:121374] 测试结束啦!
*/
//我们自己创建个线程试试看
[NSThread detachNewThreadWithBlock:^{
[self test1_1];
}];
//结果与上次一样
/**
2020-09-29 15:30:53.381501+0800 多线程[3253:131992] 开始测试啦!
2020-09-29 15:30:53.381831+0800 多线程[3253:131992] 1.同步任务+串行队列
2020-09-29 15:30:53.382813+0800 多线程[3253:131992] 当前线程:{number = 7, name = (null)}
2020-09-29 15:30:55.386012+0800 多线程[3253:131992] 同步任务二+串行,睡了2秒
2020-09-29 15:30:55.386528+0800 多线程[3253:131992] 当前线程:{number = 7, name = (null)}
2020-09-29 15:30:55.386772+0800 多线程[3253:131992] 同步任务三+串行
2020-09-29 15:30:55.387163+0800 多线程[3253:131992] 当前线程:{number = 7, name = (null)}
2020-09-29 15:30:55.387616+0800 多线程[3253:131992] 测试结束啦!
*/
}
-(void)test1_1{
NSLog(@"开始测试啦!");
//1.同步任务+串行队列
dispatch_queue_t cxQ1 = dispatch_queue_create("串行队列一", DISPATCH_QUEUE_SERIAL);
dispatch_sync(cxQ1, ^{
NSLog(@"1.同步任务+串行队列");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(cxQ1, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"同步任务二+串行,睡了2秒");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(cxQ1, ^{
NSLog(@"同步任务三+串行");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
NSLog(@"测试结束啦!");
}
示意图:
2.同步任务+并发队列
//2.同步任务+并发队列
- (IBAction)gcdTest2:(id)sender {
NSLog(@"开始测试啦,2.同步任务+并发队列");
dispatch_queue_t bfQ = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(bfQ, ^{
NSLog(@"任务一");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(bfQ, ^{
[NSThread sleepForTimeInterval:3.0];
NSLog(@"任务二");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(bfQ, ^{
NSLog(@"任务三");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
NSLog(@"测试结束啦");
/**
总结:同步任务,把任务添加到当前线程,当前在主线程,所以三个任务都添加到主线程中执行。
*/
}
打印结果:
总结:同步任务,把任务添加到当前线程,当前在主线程,所以三个任务都添加到主线程中执行。同步任务不具备开启新线程的能力,所以没有新的线程。
示意图如下:
3.异步任务+串行队列
//3.异步任务+串行队列
- (IBAction)gcdTest3:(id)sender {
NSLog(@"开始测试,3.异步任务+串行队列");
dispatch_queue_t cxq = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
dispatch_async(cxq, ^{
NSLog(@"任务一");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_async(cxq, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任务二");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
dispatch_async(cxq, ^{
NSLog(@"任务三");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
NSLog(@"测试结束啦!");
}
打印结果:
总结:
异步任务可以开启新的线程,也不会阻塞当前线程。当前是主线程,因为开启新的线程需要时间,所以先打印的主线程的两句话。开启线程以后,因为是串行队列,任务在队列中一个接一个的执行。
示意图如下:
4.异步任务+并发队列
//4.异步任务+并发队列
- (IBAction)gcdTest4:(id)sender {
NSLog(@"测试开始,异步任务+并发队列");
dispatch_queue_t bfq = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(bfq, ^{
NSLog(@"任务一");
NSLog(@"1.当前线程:%@",[NSThread currentThread]);
});
dispatch_async(bfq, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任务二");
NSLog(@"2.当前线程:%@",[NSThread currentThread]);
});
dispatch_async(bfq, ^{
NSLog(@"任务三");
NSLog(@"3.当前线程:%@",[NSThread currentThread]);
});
NSLog(@"测试结束了!");
}
打印结果:
总结:
异步任务具备开启新的线程的能力,此案例中有三个任务,开启了三个线程。并发队列把任务分配给子线程中执行。
示意图如下:
5.同步任务+主队列(死锁)
//5.同步任务+主队列:死锁
- (IBAction)gcdTest5:(id)sender {
NSLog(@"测试开始,同步任务+主队列");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务一");
NSLog(@"1.线程:%@",[NSThread currentThread]);
});
NSLog(@"测试结束");
}
结果:
为什么嘞?
我们把代码按照任务详细分一下:
网上很多人会把上面代码分为三个任务,但是我认为,分为四个任务更能够理解死锁是如何产生的,因为即使任务四不存在,依然会发生死锁。
个人分析:
当前在主线程中,任务一顺利执行,然后执行dispatch_sync这个函数,也就是任务二,函数体要执行的是任务三。任务二要等任务三执行完成才能返回。因为主线程是串行的,所以任务三是在任务二后面执行的。这就造成了任务二等待任务三执行完返回,任务三要等待任务二返回才能执行。它俩就这样僵持住了,互相等待了,造成了死锁。
示意图如下:
6.异步任务+主队列
//6.异步任务+主队列
- (IBAction)gcdTest6:(id)sender {
NSLog(@"测试开始,异步任务+主队列");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务一");
NSLog(@"1.线程:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任务二");
NSLog(@"2.线程:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务三");
NSLog(@"3.线程:%@",[NSThread currentThread]);
});
NSLog(@"测试结束。");
}
打印结果:
总结:
我们知道,主队列是串行队列,异步任务可以开启新的线程,但是不一定会开启新的线程。这里就没有开启新的线程,而是把任务添加到主线程中。异步任务不会阻塞主线程,所以先打印了主线程中的两句话,然后顺序执行三个任务。
示意图如下:
7.同步任务+全局队列(也是并发队列)
//7.同步任务+全局队列(也是并发队列)
- (IBAction)gcdTest7:(id)sender {
NSLog(@"开始,同步任务+全局队列(也是并发队列)");
dispatch_queue_t global = dispatch_get_global_queue(0, 0);
dispatch_sync(global, ^{
NSLog(@"任务一");
NSLog(@"1.线程:%@",[NSThread currentThread]);
});
dispatch_sync(global, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任务二");
NSLog(@"2.线程:%@",[NSThread currentThread]);
});
dispatch_sync(global, ^{
NSLog(@"任务三");
NSLog(@"3.线程:%@",[NSThread currentThread]);
});
NSLog(@"测试结束了!");
}
结果:
总结:
同步任务不开启新的线程,而是把任务添加到当前线程,也就是主线程中。全局队列是个并发队列,并发队列只有在异步任务的时候才能起作用,因为当前只有一个线程,并发是无效果的。因为同步任务会阻塞当前线程,直到任务完成。所以会先打印最上面的一行在主线程中的代码,接着阻塞,执行同步任务,也就是任务一、任务二和任务三,直到所有任务执行完了,在继续执行主线程的最后一行打印。
示意图如下:
8.异步任务+全局队列(也是并发队列)
//8.异步任务+全局队列(也是并发队列)
- (IBAction)gcdTest8:(id)sender {
NSLog(@"开始,异步任务+全局队列");
dispatch_queue_t global = dispatch_get_global_queue(0, 0);
dispatch_async(global, ^{
NSLog(@"任务一");
NSLog(@"1.线程:%@",[NSThread currentThread]);
});
dispatch_async(global, ^{
[NSThread sleepForTimeInterval:2.0];
NSLog(@"任务二");
NSLog(@"2.线程:%@",[NSThread currentThread]);
});
dispatch_async(global, ^{
NSLog(@"任务三");
NSLog(@"3.线程:%@",[NSThread currentThread]);
});
NSLog(@"测试结束了");
}
打印结果:
总结:
异步任务是可以开启新线程的。这里的三个异步任务开启了三个线程。全局队列也是并发队列,把任务分发给三个子线程中执行。主线程不会阻塞,所以直接打印了主线程的内容。开启子线程需要时间。由于任务二睡了2秒,所以先打印的任务一和任务三,最后是任务二。
示意图如下:
GCD的任务和队列的总结:
简单一点:
观察之后发现,
同步任务都不开启新的线程,都会阻塞当前线程,大部分都是串行执行任务。
异步任务都不会阻塞当前线程,大部分会开启新的线程。
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]);
});
});
}
结果:
打印结果:
6. 栅栏函数
dispatch_barrier_async(queue, ^{
NSLog(@"###############我是个栅栏################");
});
栅栏函数的作用是,控制任务的执行顺序.
我们用代码试试看,我们使用异步任务+并发队列。
//栅栏函数
- (IBAction)zhalan:(id)sender {
//栅栏函数,可以控制任务的执行顺序
//1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
//2.创建任务,异步任务
dispatch_async(queue, ^{
NSLog(@"任务一");
[self run:@"1"];
});
dispatch_async(queue, ^{
NSLog(@"任务二");
[self run:@"2"];
});
dispatch_async(queue, ^{
NSLog(@"任务三");
[self run:@"3"];
});
dispatch_async(queue, ^{
NSLog(@"任务四");
[self run:@"4"];
});
}
-(void)run:(NSString *)mark {
for (int i = 0; i<5; i++) {
NSLog(@"任务:%@,i = %d, 线程:%@",mark,i,[NSThread currentThread]);
}
}
打印结果:
并发执行的线程,任务的顺序是不可控的。
我们使用栅栏函数后看看。
//栅栏函数
- (IBAction)zhalan:(id)sender {
//栅栏函数,可以控制任务的执行顺序
//1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
//2.创建任务,异步任务
dispatch_async(queue, ^{
NSLog(@"任务一");
[self run:@"1"];
});
dispatch_async(queue, ^{
NSLog(@"任务二");
[self run:@"2"];
});
//栅栏函数
dispatch_barrier_async(queue, ^{
NSLog(@"###############我是个栅栏################");
});
dispatch_async(queue, ^{
NSLog(@"任务三");
[self run:@"3"];
});
dispatch_async(queue, ^{
NSLog(@"任务四");
[self run:@"4"];
});
}
-(void)run:(NSString *)mark {
for (int i = 0; i<5; i++) {
NSLog(@"任务:%@,i = %d, 线程:%@",mark,i,[NSThread currentThread]);
}
}
我们发现,使用栅栏函数之后,栅栏前面的两个任务先执行完,然后执行栅栏函数,最后在执行后面的任务。就像一座栅栏一样,把异步执行的无顺序的任务隔离开了,变成了部分有序的代码。
7. 延迟执行
有的时候,我们需要延迟几秒后在执行某些操作,这个时候,我们就可以使用GCD的dispatch_after来实现啦。
dispatch_after不会阻塞当前线程,可以在主线程,也可以在子线程中执行代码。
//延迟执行
- (IBAction)yanchi:(id)sender {
//在主线程中延迟2秒执行
NSLog(@"开始啦!");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"哈哈哈哈哈,延迟了嘛");
NSLog(@"当前线程:%@",[NSThread currentThread]);
});
NSLog(@"结束啦");
//子线程中延迟3秒执行
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(@"又一次结束啦!");
}
结果:
延迟执行,我们还有其他的方法,比如:
//延迟执行的其他方法
[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(@"延迟执行哟,线程:%@",[NSThread currentThread]);
}
结果:
也是可以延迟执行的。但是呢,这两个方法都是在主线程中执行的。
8. 一次性代码
dispatch_once
我们在创建单例的时候经常使用GCD的dispatch_once这个方法,保证代码只会执行一次。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"此代码只会执行一次。");
});
因为此段代码常用于单例中,我们创建个单例试试。
8_1. 一次性代码案例:单例
代码如下:
GCDTest.h
//
// GCDTest.h
// Multithreading
//
// Created by wenhuanhuan on 2020/10/2.
// Copyright © 2020 weiman. All rights reserved.
//
#import
NS_ASSUME_NONNULL_BEGIN
@interface GCDTest : NSObject
+(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(@"此代码只会执行一次。");
});
//创建个单例试试
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);
}
看看结果:
我们发现,我们创建的四个对象的地址都是一样的,这就是一个合理的单例啦。
9. 快速迭代
我们需要使用循环的时候,一般用for循环,for_in循环,while循环,do...while循环,swift中还有更多其他的循环。在GCD中,也给我们提供了一种快速的循环方法,dispatch_apply。
/*
第一个参数:迭代的次数
第二个参数:在哪个队列中执行
第三个参数:block要执行的任务
*/
dispatch_apply(10, queue, ^(size_t index) {
});
为什么它是快速迭代呢?
因为它会开启多个线程并发执行循环体内的操作。
如果在串行队列中,dispatch_apply就和for循环一样顺序执行,就没有意义了。
在并发队列中,dispatch_apply会开启多个线程并发执行,提高效率。
//快速迭代
- (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]);
});
}
结果:
10. 队列组
队列组可以在组中的并发线程都执行完成后,进行某些操作。
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并行队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 执行队列组任务
dispatch_group_async(group, queue, ^{
});
//队列组中的任务执行完毕之后,执行该函数
dispatch_group_notify(group, queue, ^{
});
看个简单例子:
//队列组
- (IBAction)duilie:(id)sender {
//创建队列组
dispatch_group_t group = dispatch_group_create();
//创建并发队列
dispatch_queue_t queue = dispatch_queue_create("并发", DISPATCH_QUEUE_CONCURRENT);
//执行队列组任务
dispatch_group_async(group, queue, ^{
NSLog(@"这是个队列组中的任务,编号1");
NSLog(@"任务一:线程%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"这是个队列组中的任务,编号2,睡了1秒");
NSLog(@"任务二:线程%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"这是个队列组中的任务,编号3");
NSLog(@"任务三:线程%@",[NSThread currentThread]);
});
//队列组执行完之后执行的函数
dispatch_group_notify(group, queue, ^{
NSLog(@"队列组任务执行完成。");
});
}
看看打印结果:
我们在项目中,也是会经常遇到这样的问题的。比如,我们要显示一个页面,但是这个页面有三个接口,我们需要等待三个接口的数据都返回来之后再进行显示。这个时候,就可以把三个网络请求放在队列组中,等待全部完成后,在进行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.创建队列组
dispatch_group_t group = dispatch_group_create();
//2.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("并发", 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();
//关闭上下文
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;
}
结果如下图:
注意图片的顺序。
到此为止,GCD的基本内容我们已经学习完了,如有遗漏错失,还请留言指教,谢谢!
下一节中,我们将探究NSOperation实现多线程。
祝大家生活愉快!