iOS-GCD深度解析
一、GCD的优势
GCD是苹果公司为多核的并行运算提出的解决方案;
GCD会自动利用更多的CPU内核(如双核、四核);
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
程序猿只需要告诉GCD想要执行什么任务,不需要编写任何多线程管理代码;
二、GCD的重要概念
GCD的两个核心概念:任务与队列
1、任务:
执行什么操作,执行任务有两种方式:同步执行函数和异步执行函数,他们之间的区别在于
1.1、同步函数:
只能在当前线程中执行任务,不具备开辟线程的能力,任务立刻执行,会阻塞当前线程并等待Block中的任务执行完毕,然后当前线程才能继续往下执行;
1.2、异步函数:
具备开辟线程的能力,但不一定会开辟新线程,当前线程会直接往下执行,不会等待Block中的任务执行完毕,所以不会阻塞当前线程;
2、队列:
用来存放任务,分为串行队列与并行队列
2.1、串行队列
让任务一个接一个执行(一个任务执行完毕之后,才能执行下一个任务);
2.2、并发队列
可以同时执行多个任务(自动开启多个线程同时执行任务),并发队列只有在异步函数(dispatch_async)才有效(同步函数不具备开辟线程的能力);
三、GCD使用
1、GCD使用步骤
定制任务(确定想做的事情),将任务添加到队列中(GCD会自动将队列中的任务取出,放在对应的线程中执行,任务的取出遵循队列的FIFO原则:先进先出,后进后出);
2、创建队列
创建队列
//第一个参数<#const char * _Nullable label#>: C语言字符串,唯一标识
//第二个参数<#dispatch_queue_attr_t _Nullable attr#>: 队列的类型
// 并行队列: DISPATCH_QUEUE_CONCURRENT
// 串行队列: DISPATCH_QUEUE_SERIAL 或者 NULL
dispatch_queue_t queue = dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>);
创建并行队列
dispatch_queue_t queue = dispatch_queue_create("com.lz",DISPATCH_QUEUE_CONCURRENT);
创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.lz", 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 queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
3、任务执行
队列在queue中,任务在block块中;
开启同步函数 同步函数:会阻塞当前线程,没有开辟线程的能力
dispatch_sync(queue,^{
});
开启异步函数 异步函数:不会阻塞当前线程,会开辟线程执行任务
dispatch_async(queue,^{
});
4、任务和队列组合
任务:同步函数 异步函数
队列:串行 并行
异步函数+并发队列:会开辟新的线程,并发执行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue,^{
NSLog(@"---%@---",[NSThread currentThread]);
});
异步函数+串行队列:只会开辟一条线程,任务串行执行
dispatch_queue_t queue = dispatch_queue_create("com.lz", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"---%@---",[NSThread currentThread]);
});
同步函数+并行队列:不会开辟线程,任务串行执行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_sync(queue,^{
NSLog(@"---%@---",[NSThread currentThread]);
});
同步函数+串行队列:不会开辟线程,任务串行执行
dispatch_queue_t queue = dispatch_queue_create("com.lz", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"---%@---",[NSThread currentThread]);
});
主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放在主线程中执行
异步函数+主队列:不会开辟线程,任务串行执行
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue,^{
NSLog(@"---%@---",[NSThread currentThread]);
});
同步函数+主队列:死锁
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue,^{
NSLog(@"---%@---",[NSThread currentThread]);
});
因为主队列是串行队列,任务只能一个一个执行。而在主队列中添加同步任务,则会造成主队列等待同步任务执行完成,才能继续执行主队列任务,因此就会相互等待而发生死锁。将这个方法放入子线程中,则不会发生死锁,任务串行执行;
5、任务队列组合总结
同步函数和异步函数的执行顺序
同步函数:立刻马上执行,会阻塞当前线程
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSLog(@"---start---");
dispatch_sync(queue, ^{
NSLog(@"---11---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"---22---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"---33---%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"---44---%@",[NSThread currentThread]);
});
NSLog(@"---end---");
我们看一下输出
---start---
---11---<NSThread: 0x6000022f1340>{number = 1, name = main}
---22---<NSThread: 0x6000022f1340>{number = 1, name = main}
---33---<NSThread: 0x6000022f1340>{number = 1, name = main}
---44---<NSThread: 0x6000022f1340>{number = 1, name = main}
---end---
同步函数会阻塞线程,立即执行
异步函数:当前线程会直接往下执行,不会阻塞当前线程
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSLog(@"---start---");
dispatch_async(queue, ^{
NSLog(@"---11---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"---22---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"---33---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"---44---%@",[NSThread currentThread]);
});
NSLog(@"---end---");
我们来看一下输入
---start---
---end---
---11---<NSThread: 0x6000020f2180>{number = 5, name = (null)}
---22---<NSThread: 0x6000020e1500>{number = 6, name = (null)}
---33---<NSThread: 0x6000020de780>{number = 3, name = (null)}
---44---<NSThread: 0x6000020d6480>{number = 4, name = (null)}
异步函数不会阻塞当前线程
注意:GCD中开多少条线程是由系统根据CUP繁忙程度决定的,如果任务很多,GCD会开启适当的子线程,并不会让所有任务同时执行。
6、GCD线程间的通信
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) UIImageView *imgV;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self _addSubviews];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSString *imgPath = @"图片地址";
NSURL *url = [NSURL URLWithString:imgPath];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
self.imgV.image = img;
});
});
}
#pragma mark - setter UI
-(void)_addSubviews{
[self.view addSubview:self.imgV];
}
#pragma mark - getter settr
-(UIImageView *)imgV{
if (_imgV == nil) {
_imgV = [[UIImageView alloc] init];
_imgV.frame = self.view.frame;
}
return _imgV;
}
@end
GCD线程间的通信非常简单,使用同步或异步函数,传入主队列即可。
7、GCD中其他常用函数
7.1、栅栏函数(控制任务的执行顺序)
dispatch_barrier_async(queue,^{
});
我们来看一下栅栏函数的作用
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self _barrier];
}
-(void _barrier{
dispatch_queue_t queue = dispatch_queue_create("com.lz", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (NSInteger i = 0; i<3; i++) {
NSLog(@"%zd-11--%@",i,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<3; i++) {
NSLog(@"%zd-22--%@",i,[NSThread currentThread]);
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"我是一个栅栏函数");
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<3; i++) {
NSLog(@"%zd-33--%@",i,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<3; i++) {
NSLog(@"%zd-44--%@",i,[NSThread currentThread]);
}
});
}
我们来看一下输出
0-22--<NSThread: 0x600001b95600>{number = 4, name = (null)}
0-11--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
1-22--<NSThread: 0x600001b95600>{number = 4, name = (null)}
1-11--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
2-11--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
2-22--<NSThread: 0x600001b95600>{number = 4, name = (null)}
我是一个栅栏函数
0-44--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
0-33--<NSThread: 0x600001b95600>{number = 4, name = (null)}
1-44--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
1-33--<NSThread: 0x600001b95600>{number = 4, name = (null)}
2-44--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
2-33--<NSThread: 0x600001b95600>{number = 4, name = (null)}
栅栏函数可以控制任务执行的顺序,栅栏函数之前的执行完毕之后,执行栅栏函数,然后在执行栅栏函数之后的
7.2、延迟执行
//第一个参数 延时时间
//第二个参数 队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
});
7.3、一次性代码
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
7.4、倒计时
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒执行
dispatch_source_set_event_handler(timer, ^{
});
dispatch_resume(timer);
7.5、队列组
// 创建队列组
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, ^{
});
本文借鉴自
Sky109中 iOS多线程--深度解析