1.简介
GCD(Grand Central Dispatch ),是Apple开发的一个多核编程的较新的解决方法,纯 C 语言,提供了非常多强大的函数。它主要用于优化应用程序以支持多核处理以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。
1.1 GCD的特点
- GCD会自动利用更多的CPU内核(比如双核、四核)。
- GCD自动管理线程的生命周期(创建线程,调度任务,销毁线程等)。
- 程序员只需要告诉 GCD 想要如何执行什么任务,不需要编写任何线程管理代码。
1.2 GCD的概念
- 任务(block):任务就是将要在线程中执行的代码,将这段代码用block封装好,然后将这个任务添加到指定的执行方式(同步执行和异步执行),等待CPU从队列中取出任务放到对应的线程中执行。
- 同步(sync):一个接着一个,前一个没有执行完,后面不能执行,不开线程。
- 异步(async):开启多个新线程,任务同一时间可以一起执行。异步是多线程的代名词。
- 队列:装载线程任务的队形结构。(系统以先进先出的方式调度队列中的任务执行)。在GCD中有两种队列:串行队列和并发队列。
- 并发队列:线程可以同时一起进行执行。实际上是CPU在多条线程之间快速的切换。(并发功能只有在异步(dispatch_async)函数下才有效)。
- 串行队列:线程只能依次有序的执行。
- GCD总结:将任务(要在线程中执行的操作block)添加到队列(自己创建或使用全局并发队列),并且指定执行任务的方式(异步dispatch_async,同步dispatch_sync)。
1.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);
//创建并发队列
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_CONCURRENT);
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
//GCD默认已经提供了全局并发队列,供整个应用使用,可以无需手动创建
/**
第一个参数:优先级 也可直接填后面的数字
#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 quque1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
- 任务的执行
队列在queue中,任务在block块中
//开启同步函数 同步函数:要求立刻马上开始执行
dispatch_queue_t queue = dispatch_get_main_queue();
/*
第一个参数:队列
第二个参数:block,在里面封装任务
*/
dispatch_sync(queue, ^{
});
//开启异步函数 异步函数 :等主线程执行完毕之后,回过头开线程执行任务
dispatch_async(queue, ^{
});
3.任务和队列的组合
- 任务:同步函数 异步函数
- 队列:串行 并行
- 异步函数+并发队列:会开启新的线程,并发执行
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
- 异步函数+串行队列:会开启一条线程,任务串行执行
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
- 同步函数+并发队列:不会开线程,任务串行执行
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
- 同步函数+串行队列:不会开线程,任务串行执行
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
- 异步函数+主队列:不会开线程,任务串行执行
使用主队列(跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
//1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//2.异步函数
dispatch_async(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
- 同步函数+主队列:死锁
//1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//2.同步函数
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
因为这个方法在主线程中,给主线程中添加任务,而同步函数要求立刻马上执行,因此就会相互等待而发生死锁。将这个方法放入子线程中,则不会发生死锁,任务串行执行。
- 总结:同步函数和异步函数的执行顺序
同步函数:立刻马上执行,会阻塞当前线程。
异步函数:当前线程会直接往下执行,不会阻塞当前线程。 - 注意:GCD中开多少条线程是由系统根据CUP繁忙程度决定的,如果任务很多,GCD会开启适当的子线程,并不会让所有任务同时执行。
1.4 GCD线程间的通信
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
// 获得图片URL
NSURL *url = [NSURL URLWithString:@"//upload-images.jianshu.io/upload_images/2301429-d5cc0a007447e469.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"];
// 将图片URL下载为二进制文件
NSData *data = [NSData dataWithContentsOfURL:url];
// 将二进制文件转化为image
UIImage *image = [UIImage imageWithData:data];
NSLog(@"%@",[NSThread currentThread]);
// 返回主线程 这里用同步函数不会发生死锁,因为这个方法在子线程中被调用。
// 也可以使用异步函数
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"%@",[NSThread currentThread]);
});
});
}
@end
通过上面的例子我们可以得出,GCD线程间的通信非常简单,使用同步或异步函数,传入主队列即可。
1.5 GCD其他常用函数
- 1.dispatch_barrier_async
当任务需要异步进行,但是这些任务需要分成两组来执行,第一组完成之后才能进行第二组的操作。这时候就用了到GCD的栅栏方法dispatch_barrier_async。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 异步执行
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步1 %@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步2 %@",[NSThread currentThread]);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"------------barrier------------%@", [NSThread currentThread]);
NSLog(@"******* 并发异步执行,但是34一定在12后面 *********");
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步3 %@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"栅栏:并发异步4 %@",[NSThread currentThread]);
}
});
}
上面代码的打印结果如下,开启了多条线程,所有任务都是并发异步进行。但是第一组完成之后,才会进行第二组的操作。
栅栏:并发异步1 {number = 3, name = (null)}
栅栏:并发异步2 {number = 6, name = (null)}
栅栏:并发异步1 {number = 3, name = (null)}
栅栏:并发异步2 {number = 6, name = (null)}
栅栏:并发异步1 {number = 3, name = (null)}
栅栏:并发异步2 {number = 6, name = (null)}
------------barrier------------{number = 6, name = (null)}
******* 并发异步执行,但是34一定在12后面 *********
栅栏:并发异步4 {number = 3, name = (null)}
栅栏:并发异步3 {number = 6, name = (null)}
栅栏:并发异步4 {number = 3, name = (null)}
栅栏:并发异步3 {number = 6, name = (null)}
栅栏:并发异步4 {number = 3, name = (null)}
栅栏:并发异步3 {number = 6, name = (null)}
- 注意栅栏函数只能是自定义的并发队列,全局队列也不行。
- GCD延时执行
当需要等待一会再执行一段代码时,就可以用到这个方法了:dispatch_after。
- GCD延时执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 5秒后异步执行
NSLog(@"我已经等待了5秒!");
});
//延迟执行的其他方法:
// 2s中之后调用run方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// repeats:YES 是否重复
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
- 3.dispatch_once
使用dispatch_once能保证某段代码在程序运行过程中只被执行1次。可以用来设计单例。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"程序运行过程中我只执行了一次!");
});
- 4.GCD快速迭代
GCD有一个快速迭代的方法dispatch_apply,dispatch_apply可以同时遍历多个数字,开启多条线程,并发执行,相比于for循环在耗时操作中极大的提高效率和速度。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"
************** GCD快速迭代 ***************
");
// 并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// dispatch_apply几乎同时遍历多个数字
dispatch_apply(7, queue, ^(size_t index) {
NSLog(@"dispatch_apply:%zd======%@",index, [NSThread currentThread]);
});
}
//打印结果
dispatch_apply:0======{number = 1, name = main}
dispatch_apply:1======{number = 1, name = main}
dispatch_apply:2======{number = 1, name = main}
dispatch_apply:3======{number = 1, name = main}
dispatch_apply:4======{number = 1, name = main}
dispatch_apply:5======{number = 1, name = main}
dispatch_apply:6======{number = 1, name = main}
- 队列组(同栅栏函数)
异步执行几个耗时操作,当这几个操作都完成之后再回到主线程进行操作,就可以用到队列组了。
队列组有下面几个特点:
1). 所有的任务会并发的执行(不按序)。
2). 所有的异步函数都添加到队列中,然后再纳入队列组的监听范围。
3). 使用dispatch_group_notify函数,来监听上面的任务是否完成,如果完成, 就会调用这个方法。
- (void)testGroup {
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"队列组:有一个耗时操作完成!");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"队列组:有一个耗时操作完成!");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"队列组:前面的耗时操作都完成了,回到主线程进行相关操作");
});
}
//打印结果
队列组:有一个耗时操作完成!
队列组:有一个耗时操作完成!
队列组:前面的耗时操作都完成了,回到主线程进行相关操作
- 信号量:dispatch_semaphore
我们在开发中,会遇到这样的需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。换句话说,相当于,将将异步执行任务转换为同步执行任务。比如说:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。
- 信号量:dispatch_semaphore
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
下面,我们来利用 Dispatch Semaphore 实现线程同步,将异步执行任务转换为同步执行任务。
/**
* semaphore 线程同步
*/
- (void)semaphoreSync {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
}