一、GCD介绍
1. 简介
并发处理能够同时处理多个任务。异步设计方法可以充分地发挥多核优势。GCD(Grand Central Dispatch)是一种异步方法,是专为多核CPU设计的并发处理技术。GCD使用纯C语言编写,提供了非常多强大的函数。
2. GCD的优势
- GCD是苹果公司为多核并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
3. 注意
- GCD是纯 C 语言的,因此我们在编写GCD相关代码的时候,面对的函数,而不是方法
- GCD中的函数大多数都以
dispatch
开头 - 将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的FIFO原则:先进先出,后进后出
4. 添加任务到队列
- 用同步的方式
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- 用异步的方式
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
说明:把右边的block
(任务)提交给左边的queue
(队列)进行执行。
5. 任务的执行
- 同步:当前线程中执行
- 异步:另开一条线程执行
6. serial dispatch queue
- 使用
dispatch_queue_create
函数创建串行队列
// 队列名称, 队列属性,一般用NULL即可
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
示例:
dispatch_queue_t queue = dispatch_queue_create("wendingding", NULL); // 创建
dispatch_release(queue); // 非ARC需要释放手动创建的队列
- 使用主队列(跟主线程相关联的队列)
//主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
//使用dispatch_get_main_queue()获得主队列
示例:
dispatch_queue_t queue = dispatch_get_main_queue();
7. concurrent dispatch queue
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
//使用dispatch_get_global_queue函数获得全局的并发队列
//获取默认优先级的全局并发dispatch queue
dispatch_queue_t queue = 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 // 后台
8. 队列执行效果
同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)
// 调用前,查看下当前线程
NSLog(@"当前调用线程:%@", [NSThread currentThread]);
// 创建一个串行queue
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", NULL);
dispatch_async(queue, ^{
NSLog(@"开启了一个异步任务,当前线程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"开启了一个同步任务,当前线程:%@", [NSThread currentThread]);
});
运行结果:
2016-07-31 09:03:37.348 thread[6491:c07] 当前调用线程:<NSThread: 0x714fa80>{name = (null), num = 1}
2016-07-31 09:03:37.349 thread[6491:1e03] 开启了一个异步任务,当前线程:<NSThread: 0x74520a0>{name = (null), num = 3}
2016-07-31 09:03:37.350 thread[6491:c07] 开启了一个同步任务,当前线程:<NSThread: 0x714fa80>{name = (null), num = 1}
添加一个任务到Queue,尽可能地使用
dispatch_async
或dispatch_async_f
函数异步地调度任务。因为添加任务到Queue
中时,无法确定这些代码什么时候能够执行。因此异步地添加block
或函数,可以让你立即调度这些代码的执行,然后调用线程可以继续去做其它事情。特别是应用主线程一定要异步地dispatch
任务,这样才能及时地响应用户事件
绝对不要在任务中调用
dispatch_sync
或dispatch_sync_f
函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做
二、主队列
- 主队列是GCD自带的一种特殊的串行队列,放在主队列中得任务,都会放到主线程中执行。如果把任务放到主队列中进行处理,那么不论处理函数是异步的还是同步的都不会开启新的线程。
- 调用
dispatch_get_main_queue
函数获得应用主线程的dispatch queue
,添加到这个queue的任务由主线程串行化执行
// 异步下载图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//执⾏耗时的异步操作...
NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程显示图片
// 主线程拿到加载的image刷新UI界面。
self.imageView.image = image;
});
});
任务中使用Objective-C对象
GCD支持Cocoa内存管理机制,因此可以在提交到queue的block中自由地使用Objective-C对象。每个dispatch queue维护自己的autorelease pool确保释放autorelease对象,但是queue不保证这些对象实际释放的时间。如果应用消耗大量内存,并且创建大量autorelease对象,你需要创建自己的autorelease pool,用来及时地释放不再使用的对象。
三、Dispatch Group的使用
使用dispatch_group_async
函数将多个任务关联到一个 Dispatch Group 和相应的 queue 中,group 会并发地同时执行这些任务。
问题:如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
如果想要快速高效地实现上述需求,可以考虑用队列组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
});
例如:
// 封装一个方法,传入一个url参数,返回一张网络上下载的图片
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
// 这里并没有自动释放UIImage对象
return [[UIImage alloc] initWithData:data];
}
- (void)downloadImages {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 异步下载图片
dispatch_async(queue, ^{
// 创建一个组
dispatch_group_t group = dispatch_group_create();
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
// 关联一个任务到group,下载第一张图片
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
image1 = [self imageWithURLString:url1];
});
// 关联一个任务到group,下载第二张图片
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
image2 = [self imageWithURLString:url2];
});
// 等待组中的任务执行完毕,回到主线程执行block回调
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
self.imageView1.image = image1;
self.imageView2.image = image2;
//合并两张图片
//注意最后一个参数是浮点数(0.0),不要写成0
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
[image1 drawInRect:CGRectMake(0, 0, 100, 100)];
[image2 drawInRect:CGRectMake(100, 0, 100, 100)];
self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();
NSLog(@"图片合并完成---%@",[NSThread currentThread]);
});
});
}
dispatch_group_notify
函数用来指定一个额外的block,该block将在group中所有任务完成后执行
四、GCD其他用法
- 用于延时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//延迟执行的方法
});
延迟执行:不需要再写方法,且它还传递了一个队列,我们可以指定并安排其线程。
如果队列是主队列,那么就在主线程执行,如果队列是并发队列,那么会新开启一个线程,在子线程中执行。
- 一次性代码
//使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
整个程序运行过程中,只会执行一次。
五、回答常见问题
-
dispatch_barrier_async
的作用是什么?
在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用barrier来等待之前任务完成,避免数据竞争等问题。dispatch_barrier_async
函数会等待追加到Concurrent Dispatch Queue
并行队列中的操作全部执行完之后,然后再执行 dispatch_barrier_async函数追加的处理,等dispatch_barrier_async
追加的处理执行结束之后,Concurrent Dispatch Queue
才恢复之前的动作继续执行。
(注意:使用dispatch_barrier_async
,该函数只能搭配自定义并行队列dispatch_queue_t
使用。不能使用:dispatch_get_global_queue
,否则dispatch_barrier_async
的作用会和dispatch_async
的作用一模一样。) -
苹果为什么要废弃
dispatch_get_current_queue
?
dispatch_get_current_queue
容易造成死锁
网友评论