GCD基本概念
- 什么是GCD?
全称是Grand Central Dispatch,翻译为*调度;纯C实现,提供了非常强大的函数。 - GCD的优势
- GCD是苹果公司为多核并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(如双核、四核等)
- GCD会自动管理线程的生命周期(线程创建、任务调度、线程销毁)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
任务和队列
理解GCD中核心的两个概念
- 任务:执行什么操作(下载图片、耗时循环等)
- 队列:用来存放任务(程序中不可能有一个任务,比如下载多张图片等)
GCD的使用
使用GCD有两个步骤
- 定制任务
确定想做的事情 - 将任务添加到队列中
GCD会自动将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则:先进先出,后进后出
执行任务
GCD中有2个用来执行任务的常用函数
- 用同步的方式执行
dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block);
- 用异步方式执行
dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block);
参数:dispatch_queue_t:队列,将任务加入到哪个队列
参数:block 封装任务的,执行任务的blcok
- 同步和异步的区别
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力
队列的类型
GCD的队列可以分为2大类型
- 并发队列(Concurrent Dispatch Queue)
- 可以让多个任务并发执行(自动开启多个线程同时执行任务)
- 并发功能只有在异步
dispatch_sync
函数下才有效
- 串行队列(Serial Dispatch Queue)
- 让任务一个接一个的执行
总结容易混淆的术语
-
四个容易混淆术语:同步、异步、并发、串行
同步和异步主要影响:能不能开启新线程- 同步:只能在当前线程中执行任务,不具备开启新线程的能力
- 异步:可以在新的线程中执行任务,具备开启新线程的能力
并发和串行的主要影响:任务的执行方式 - 并发:允许多个任务并发执行(同时)
- 串行:任务一个接一个执行,一个任务执行完毕后,再执行下一个任务
这里的同步、异步指的是GCD中的同步和异步函数,并发、串行指的是GCD中的队列
基本使用方式
- 异步
-(void)downloadImage{
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
NSString *imageUrlStr=@"https://t7.baidu.com/it/u=139984722,3523412696&fm=193&f=GIF";
NSURL *url = [NSURL URLWithString:imageUrlStr];
NSError *error=nil;
NSData *data = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached
error:&error];
UIImage *image=nil;
if (!error) {
image=[UIImage imageWithData:data scale:[UIScreen mainScreen].scale];
}
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
// [self performSelectorOnMainThread:@selector(showImag:) withObject:image waitUntilDone:YES];
//使用imageView可省略一步写showImage:方法
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
NSLog(@"%f",end-start);
}
1.异步函数并行队列,会开启多条线程,队列中的任务是并发执行
//异步函数+并行队列:
-(void)asyncConcurrent{
//1. 创建队列
//第一个参数:C语音字符串,标签
//第二个参数:队列类型;DISPATCH_QUEUE_CONCURRENT并发对下列;DISPATCH_QUEUE_SERIAL串行队列
dispatch_queue_t queue = dispatch_queue_create("com.threadDome.async", DISPATCH_QUEUE_CONCURRENT);
//2. 1.封装任务->添加任务到队列中
//第一个参数:队列
//第二个参数:要执行的任务
dispatch_async(queue, ^{
NSLog(@"downloadImage1 = %@",[NSThread currentThread]);
[self downloadImage];
});
dispatch_async(queue, ^{
NSLog(@"downloadImage2 = %@",[NSThread currentThread]);
[self downloadImage];
});
dispatch_async(queue, ^{
NSLog(@"downloadImage3 = %@",[NSThread currentThread]);
[self downloadImage];
});
}
通过打印日志看出,执行顺序无序的说明是并发执行
2. 异步函数串行队列:开启新线程,任务串行执行
//异步函数+串行队列:
-(void)asyncSerial{
//1. 创建队列
//第一个参数:C语音字符串,标签
//第二个参数:队列类型;DISPATCH_QUEUE_CONCURRENT并发对下列;DISPATCH_QUEUE_SERIAL串行队列
dispatch_queue_t queue = dispatch_queue_create("com.threadDome.async", DISPATCH_QUEUE_SERIAL);
//2. 1.封装任务->添加任务到队列中
//第一个参数:队列
//第二个参数:要执行的任务
dispatch_async(queue, ^{
NSLog(@"downloadImage1 = %@",[NSThread currentThread]);
// [self downloadImage];
});
dispatch_async(queue, ^{
NSLog(@"downloadImage2 = %@",[NSThread currentThread]);
// [self downloadImage];
});
dispatch_async(queue, ^{
NSLog(@"downloadImage3 = %@",[NSThread currentThread]);
// [self downloadImage];
});
}
由日志可看出,任务是串行执行,并是在同一个新线程中执行
3. 同步方法+并行队列:任务是串行执行,并是在当前线程中执行(哪个线程调用就是那个线程中)不会开启新线程
//同步函数+并行队列:
-(void)syncConcurrent{
//1. 创建队列
//第一个参数:C语音字符串,标签
//第二个参数:队列类型;DISPATCH_QUEUE_CONCURRENT并发对下列;DISPATCH_QUEUE_SERIAL串行队列
dispatch_queue_t queue = dispatch_queue_create("com.threadDome.async", DISPATCH_QUEUE_CONCURRENT);
//2. 1.封装任务->添加任务到队列中
//第一个参数:队列
//第二个参数:要执行的任务
dispatch_sync(queue, ^{
NSLog(@"downloadImage1 = %@",[NSThread currentThread]);
// [self downloadImage];
});
dispatch_sync(queue, ^{
NSLog(@"downloadImage2 = %@",[NSThread currentThread]);
// [self downloadImage];
});
dispatch_sync(queue, ^{
NSLog(@"downloadImage3 = %@",[NSThread currentThread]);
// [self downloadImage];
});
}
4. 同步函数+串行队列:不会开启新线程,任务是串行执行。
//同步函数+串行队列:
-(void)syncSerial{
//1. 创建队列
//第一个参数:C语音字符串,标签
//第二个参数:队列类型;DISPATCH_QUEUE_CONCURRENT并发对下列;DISPATCH_QUEUE_SERIAL串行队列
dispatch_queue_t queue = dispatch_queue_create("com.threadDome.async", DISPATCH_QUEUE_SERIAL);
//2. 1.封装任务->添加任务到队列中
//第一个参数:队列
//第二个参数:要执行的任务
dispatch_sync(queue, ^{
NSLog(@"downloadImage1 = %@",[NSThread currentThread]);
// [self downloadImage];
});
dispatch_sync(queue, ^{
NSLog(@"downloadImage2 = %@",[NSThread currentThread]);
// [self downloadImage];
});
dispatch_sync(queue, ^{
NSLog(@"downloadImage3 = %@",[NSThread currentThread]);
// [self downloadImage];
});
}
获取并发队列方式
- 通过
dispatch_queue_create
函数创建并发队列 - 获取全局并发队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_get_global_queue
参数说明- 第一个参数:优先级:DISPATCH_QUEUE_PRIORITY_BACKGROUND(该优先级最低)
- 第二个参数:为将来保留使用的标志。始终为该参数指定0
使用dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
获得的并发队列,与1中创建的队列是等价的,区别是,1是实打实创建了一个队列。使用dispatch_get_global_queue
是获取全局现有的一个队里(这个队列在GCD中本身就存在,并且对应有高优先级,默认优先级,低优先级,和后台优先级一共有四个并发队列),在ios6.0之前,在gcd中凡是带有creat、retain
的函数都需要做一次release
操作。而主队列和全局并发队列不需要我们手动release
。ios6.0之后GCD已经被纳入到了ARC的内存管理范畴中,不用再手动release
释放了。
注意:GCD开多少条线程由系统决定,系统决定开几条合适就开几条
获取串行队列
GCD中获取创新队列有2中途径
- 通过
dispatch_queue_create
函数创建串行队列dispatch_queue_create("com.threadDome.async", NULL)队列类型传递NULL或DISPATCH_QUEUE_SERIAL
- 使用主队列(和主线程相关的队列)
- 主队列是GCD自带的一种特殊串行队列
- 放在主队列中的任务,都会放到主线程执行
异步同步函数使用住队列
- 异步函数+主队列
- 不会开启新线程,所有任务都在主线程执行,串行执行
异步函数:如果当前没有执行完毕,后面代码也能执行
//异步步函数+主队列:
-(void)asyncMainQueue{
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"downloadImage1----- %@",[NSThread mainThread]);
});
dispatch_async(queue, ^{
NSLog(@"downloadImage2----- %@",[NSThread mainThread]);
});
dispatch_async(queue, ^{
NSLog(@"downloadImage3----- %@",[NSThread mainThread]);
});
}
4. 同步函数+主队列
同步函数不能再主队列中执行,会产生死锁
主队列特点:如果主队列发下当前主线程有任务执行那么主队列会暂停调用队列中的任务,直到主线程空闲为止
同步函数:立刻执行,如果没有执行完毕,那么后面的也别想执行
同步函数将任务加入主队列中,因同步函数是立刻执行必须等待代码执行完毕才能继续往下执行,主队列将任务取出来让主线程来执行它,而主线程又没空,这样就导致死锁。
解决方式:在其他子线程中执行
[NSThread detachNewThreadSelector:@selector(syncMainQueue) toTarget:self withObject:nil];
//异步步函数+主队列:
-(void)syncMainQueue{
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"star");
dispatch_sync(queue, ^{
NSLog(@"downloadImage1----- %@",[NSThread mainThread]);
});
}
总结:
并发队列 | 手动创建串行队列 | 主队列 | |
---|---|---|---|
同步 | 1.没有开启新线程; 2.串行执行任务 |
1.没有开启新线程; 2.串行执行任务 |
在主线程调用会死锁; 子线程执行: 1.没有开启新线程; 2.串行执行任务 |
异步 | 1.开启新线程; 2.并发执行任务 |
1.开启新线程; 2.串行执行任务 |
1.不会开启新线程; 2.串行执行任务 |
GCD线程间的通讯
前面NSThread说过线程间通讯。GCD线程间通讯更简单,直接嵌套就行。
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *imageUrlStr=@"https://t7.baidu.com/it/u=139984722,3523412696&fm=193&f=GIF";
NSURL *url = [NSURL URLWithString:imageUrlStr];
NSError *error=nil;
NSData *data = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached
error:&error];
UIImage *image=nil;
if (!error) {
image=[UIImage imageWithData:data scale:[UIScreen mainScreen].scale];
}
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.imageView.image=image;
});
});
GCD常用函数
- 延迟执行
dispatch_after
/*第一个参数:DISPATCH_TIME_NOW 从现在开始计算时间
第二个参数:延迟的时间 GCD的时间单位是纳秒所以需要乘以一个参数NSEC_PER_SEC
*/
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
/*第二个参数队列*/
dispatch_after(time, dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT), ^{
NSLog(@"GCD延迟咨询-%@",[NSThread currentThread]);
});
-
dispatch_once
一次性代码,运用程序只会执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"GCD once");
});
GCD 栅栏函数
- 什么是栅栏函数,有什么作用?
dispatch_barrier_async
和dispatch_barrier_sync
栅栏函数,一个是同步一个是异步方法;
作用:我们在做多任务并发执行时,如有task1、task2、task3、task4四个并发任务,通常任务执行是无序的也执行完成时间也不一致,但需要某个或某几个任务必须在其他任务完成后在执行,栅栏函数就起租用了,只要在需要等待其他任务执行完成后再执行的任务前,加上栅栏就能达到该目的。在使用栅栏函数时,apple官方明确规定栅栏函数只有在使用create函数自己创建的并发队列一起使用的时候才有效没有给出具体原因
示例如下:
dispatch_queue_t queue = dispatch_queue_create("com.thread.dome", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 模拟耗时操作
for (int i=0; i<100; i++) {
NSLog(@"task1 === %d",i);
}
});
dispatch_async(queue, ^{
// 模拟耗时操作
for (int i=0; i<100; i++) {
NSLog(@"task2 === %d",i);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"========");
});
dispatch_async(queue, ^{
// 模拟耗时操作
for (int i=0; i<100; i++) {
NSLog(@"task3 === %d",i);
}
});
dispatch_async(queue, ^{
// 模拟耗时操作
for (int i=0; i<100; i++) {
NSLog(@"task4 === %d",i);
}
});
注意:栅栏函数不能使用全局队列,否则无效
GCD快速迭代
迭代就是一个for循环,通常循环是同步串行执行的。GCD快速迭代是异步并发执行的,执行速度更快。
函数名为dispatch_apply
,函数接收三个参数,参数含义如下:
- 第一个参数:循环总次数
- 第二个参数:队列类型,必须是并发队列,串行队列无意义,主队列会造成死锁。
- 第三个参数:执行任务的block
/*
第一个参数:循环总次数
第二个参数:队列类型,必须是并发队列,串行队列无意义,主队列会造成死锁。
第三个参数:执行任务的block
*/
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
NSLog(@"%zu ==%@",index,[NSThread currentThread]);
});
GCD的快速迭代对耗时操作有奇效
队列组
队列组的作用和栅栏函数有点相似,也是控制任务执行,例如,有个需求,有三个并发任务,我需要知道这三个任务都执行完后在执行其他的任务,这时队列组就起到作用了。
使用队列有如下步骤
- 创建并发队列
- 创建队列组
- 使用队列函数
- 在需要第地方添加队列通知函数执行任务
其中涉及到的类型和函数有,dispatch_group_t gorup = dispatch_group_create()
、dispatch_group_async
、dispatch_group_notify
其中dispatch_group_async
执行了如下功能
1.封装任务
2.把任务添加到队列中
3.会监听任务的执行情况,通知到group
使用示例:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t gorup = dispatch_group_create();
dispatch_group_async(gorup, queue, ^{
NSLog(@"task1 ------%@",[NSThread currentThread]);
});
dispatch_group_async(gorup, queue, ^{
NSLog(@"task2 ------%@",[NSThread currentThread]);
});
dispatch_group_async(gorup, queue, ^{
NSLog(@"task3 ------%@",[NSThread currentThread]);
});
dispatch_group_notify(gorup, queue, ^{
NSLog(@"其他任务执行完毕了");
});
第二种通过函数对的形式
还有一种实现队列组完成情是使用函数对的形式,涉及的函数如下:
-
dispatch_group_enter
在该函数方法后面的异步任务会被纳入到队列组的监听范围,进入群组 -
dispatch_group_leave
告知group任务完成了离开群组
//1.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.创建队列组
dispatch_group_t group = dispatch_group_create();
//3. 在该函数方法后面的异步任务会被纳入到队列组的监听范围,进入群组
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task1 -- %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task2 -- %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"task2 -- %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@" 其他任务执行完毕了 ");
});
NSLog(@"=== end ====");
注意:dispatch_group_enter和dispatch_group_leave必须成对使用
也可将dispatch_group_notify
替换dispatch_group_wait
函数,在dispatch_group_wait
后面加其他任务完成后的执行代码。dispatch_group_wait
参数说明:
1.队列组
2.时间:dispatch_time_t,有两个宏DISPATCH_TIME_FOREVER
死等知道队列组中所有任务执行完毕后才执行。
下面是一个下载两张图片冰进行合成的示例:
-(void)synteticImage{
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group=dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
[self downLoadImage:@"https://t7.baidu.com/it/u=4086399150,3589527948&fm=193&f=GIF" completion:^(UIImage *img) {
self.image1 = img;
dispatch_group_leave(group);
}];
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
[self downLoadImage:@"https://t7.baidu.com/it/u=4042286517,3975636867&fm=193&f=GIF" completion:^(UIImage *img) {
self.image2 = img;
dispatch_group_leave(group);
}];
});
dispatch_group_notify(group, queue, ^{
UIGraphicsBeginImageContext(CGSizeMake(400, 300));
[self.image1 drawInRect:CGRectMake(0, 0, 200, 200)];
[self.image2 drawInRect:CGRectMake(200, 0, 200, 200)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
CFAbsoluteTime end=CFAbsoluteTimeGetCurrent();
NSLog(@"time = %f",end-start);
});
}
-(void)downLoadImage:(NSString *)imgUrlStr completion:(void(^)(UIImage *img))block{
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
NSURL *imgURL = [NSURL URLWithString:imgUrlStr];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
UIImage *img = [UIImage imageWithData:imgData];
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"donwloadImage time %f",end-start);
if (block) {
block(img);
}
}
或
-(void)synteticImage{
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group=dispatch_group_create();
dispatch_group_async(group,queue, ^{
self.image1= [self downLoadImage:@"https://t7.baidu.com/it/u=4086399150,3589527948&fm=193&f=GIF"];
});
dispatch_group_async(group,queue, ^{
self.image2=[self downLoadImage:@"https://t7.baidu.com/it/u=4042286517,3975636867&fm=193&f=GIF"];
});
dispatch_group_notify(group, queue, ^{
UIGraphicsBeginImageContext(CGSizeMake(400, 300));
[self.image1 drawInRect:CGRectMake(0, 0, 200, 200)];
[self.image2 drawInRect:CGRectMake(200, 0, 200, 200)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
CFAbsoluteTime end=CFAbsoluteTimeGetCurrent();
NSLog(@"time = %f",end-start);
});
}
-(UIImage *)downLoadImage:(NSString *)imgUrlStr{
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
NSURL *imgURL = [NSURL URLWithString:imgUrlStr];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
UIImage *img = [UIImage imageWithData:imgData];
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"donwloadImage time %f",end-start);
return img;
}