「iOS」GCD
文章目录
- 「iOS」GCD
-
- 一、基础知识
-
- [1. 队列](#1. 队列)
- [2. 同步、异步](#2. 同步、异步)
- 二、iOS的多线程方案
- 三、GCD
-
- [1. 优势](#1. 优势)
- [2. 使用](#2. 使用)
- 四、总结
一、基础知识
1. 队列
定义:队列,又称为伫列(queue),是先进先出 的线性表,在具体应用中通常用链表 或者数组 来实现。装载线程任务的队形结构。队列只允许在后端(称为rear)进行插入 操作,在前端(称为front)进行删除操作。队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。
**类型:**队列的类型决定了任务的执行方式(并发、串行),队列包括以下几种:
- 并发队列: 线程执行可以同时一起进行执行,不需要上一个执行完,就能执行下一个的。
- 串行队列: 线程执行只能依次逐一先后有序的执行,等待上一个执行完,再执行下一个。
- 主队列:绑定主线程,所有任务都在主线程中执行,有经过特殊处理的串行队列。
- 全局队列:系统提供的并发队列。
2. 同步、异步
同步 sync: 只能在当前线程按先后顺序依次执行任务,不具备开启新线程的能力。
异步 async: 在新的线程中执行任务,具备开启新线程的能力。
二、iOS的多线程方案
-
pthread:是线程的POSIX标准,是一套通用的多线程API,可以在Unix/Linux/Windows等平台跨平台使用。iOS中基本不使用。 -
NSThread:苹果封装的面向对象的线程类,可以直接操作线程,比起GCD,NSThread效率更高,由程序员自行创建,当线程中的任务执行完毕后,线程会自动退出,程序员也可手动管理线程的生命周期。使用频率较低。 -
GCD:全称Grand Central Dispatch,由C语言实现,是苹果为多核的并行运算提出的解决方案,CGD会自动利用更多的CPU内核,自动管理线程的生命周期,程序员只需要告诉GCD需要执行的任务,无需编写任何管理线程的代码。GCD也是iOS使用频率最高的多线程技术。 -
NSOperation:基于GCD封装的面向对象的多线程技术,常配合NSOperationQueue使用,使用频率较高。
三、GCD
1. 优势
GCD是苹果公司为多核的并行运算提出的解决方案GCD会自动利用更多的CPU内核(比如双核、四核)GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)- 程序员只需要告诉
GCD想要执行什么任务,不需要编写任何线程管理代码
2. 使用
同步执行API
objc
//queue:队列 block:任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
异步执行
objc
//queue:队列 block:任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
dispatch_block_t任务
无参数、无返回值的 Block 回调,用于存放具体需要执行的线程任务,底层定义如下:
objc
typedef void (^dispatch_block_t)(void);
dispatch_queue_t队列
任务存放的队列容器。自定义队列通过 dispatch_queue_create 创建,参数分别为队列名称 、队列类型 ;其中串行队列宏 DISPATCH_QUEUE_SERIAL 等价于 NULL,传 NULL 即为串行队列。
我们已经知道队列在iOS中主要分为4种
自定义的队列都是调用dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr)方法来进行创建,两个参数分别为自定义队列名称和队列类型。其中串行队列DISPATCH_QUEUE_SERIAL为宏定义的NULL,所以传NULL也表示为串行队列。
现在,我们来看这2种执行模式,3种队列是相互搭配是什么效果:
同步 + (主队列或自定义串行队列)(死锁)
objc
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"任务2",[NSThread currentThread]);
});
NSLog(@"任务3:%@",[NSThread currentThread]);
}
运行结果
打印任务1后死锁崩溃
原因分析:主队列是串行队列,遵循 FIFO 规则。当前 viewDidLoad 本身是主队列的任务,未执行完毕;此时通过同步方式向主队列新增任务2,同步执行会阻塞主线程,等待任务2执行完成。但任务2需要等待 viewDidLoad 原有任务执行完毕才能执行,二者相互等待,最终形成死锁。
异步 + (主队列或自定义串行队列)
objc
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"任务2:%@",[NSThread currentThread]);
});
NSLog(@"任务3:%@",[NSThread currentThread]);
}
运行结果
任务1:<_NSMainThread: 0x60000170c000>{number = 1, name = main}
任务3:<_NSMainThread: 0x60000170c000>{number = 1, name = main}
任务2:<_NSMainThread: 0x60000170c000>{number = 1, name = main}
异步执行不会阻塞当前主线程,主线程执行完任务1后,无需等待任务2完成,直接执行任务3。主队列是串行队列,所有任务仍在主线程排队执行,因此任务3执行完成后,再执行任务2,不会开启新线程、不会死锁。
同步+(全局队列或者自定义并发队列) (顺序执行、无新线程)
objc
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_sync(queue, ^{
for (int i = 0; i<3; i++) {
NSLog(@"任务一:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i<3; i++) {
NSLog(@"任务二:%@",[NSThread currentThread]);
}
});
}
运行结果
任务一:<_NSMainThread: 0x600001704000>{number = 1, name = main}
任务一:<_NSMainThread: 0x600001704000>{number = 1, name = main}
任务一:<_NSMainThread: 0x600001704000>{number = 1, name = main}
任务二:<_NSMainThread: 0x600001704000>{number = 1, name = main}
任务二:<_NSMainThread: 0x600001704000>{number = 1, name = main}
任务二:<_NSMainThread: 0x600001704000>{number = 1, name = main}
同步执行本身不具备开启新线程的能力,任务直接在主线程执行,且会阻塞线程,必须等待上一个任务完成才会执行下一个任务。虽然队列是并发队列,但受同步执行特性限制,任务只能顺序执行,且不会阻塞主线程正常运行。
异步+(全局队列或者自定义并发队列)(并发执行、开启新线程)
objc
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i<3; i++) {
NSLog(@"任务一:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i<3; i++) {
NSLog(@"任务二:%@",[NSThread currentThread]);
}
});
}
运行结果
任务二:<NSThread: 0x600001726340>{number = 6, name = (null)}
任务一:<NSThread: 0x60000170dd00>{number = 5, name = (null)}
任务二:<NSThread: 0x600001726340>{number = 6, name = (null)}
任务一:<NSThread: 0x60000170dd00>{number = 5, name = (null)}
任务二:<NSThread: 0x600001726340>{number = 6, name = (null)}
任务一:<NSThread: 0x60000170dd00>{number = 5, name = (null)}
可以看出任务1和任务2交错执行,并非同步执行那样执行完任务1再执行任务2。而且通过线程编号可以看出,的确开启了新的线程。说明异步执行具备开启新线程的能力。
但是通过上面异步+主队列的打印结果我们可以发现,在主队列时,异步执行也并没有开启新的线程,而仍然是同一个主线程。说明如果在主队列中异步执行,是不会开启新线程的
原因是因为主队列是串行队列,必须执行完一个任务再执行另一个任务,异步执行只是没有造成堵塞,但是在主队列还是是要一步步走的。
同一串行队列嵌套
objc
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1");
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"任务2");
dispatch_sync(queue, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");
}
运行结果
任务1
任务5
任务2
然后崩溃
这里造成死锁崩溃的原因和上面同步+串行队列的原因一样。由于主队列是串行的,所以代码必然是从上到下依次执行的。
打印完任务1后,执行dispatch_async为异步,不会阻塞线程,但是会加入自定义的串行队列中,所以会执行任务5,接着执行异步代码块中逻辑,打印任务2,接着执行dispatch_sync代码块,但是由于其是同步,串行队列中已经有了dispatch_async任务未执行完毕,此时dispatch_sync阻塞当前线程等待任务3执行,造成了相互等待,所以就死锁崩溃了
不同串行队列嵌套
objc
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1");
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"任务2");
dispatch_sync(queue2, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");
}
运行结果
任务1,任务5,任务2,任务3,任务4
当嵌套执行时,同步异步在不同的串行队列执行,并不会造成死锁崩溃。而是按照串行的顺序,执行代码。这事因为dispatch_sync阻塞的并不是当前的线程,而是其他的线程,所以不会造成死锁等待。
同一并发队列嵌套
objc
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1");
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任务2");
dispatch_sync(queue, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");
}
运行结果
任务1,任务5,任务2,任务3,任务4
主线程执行任务1之后,需要异步dispatch_async执行任务2;所以先执行主线程的任务5,然后执行任务2;接着需要在并发队列中同步dispatch_sync执行任务3,所以会造成阻塞,等待其执行完成,然后执行并发队列中的任务4。
不同并发、串行队列嵌套
objc
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1");
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任务2");
dispatch_sync(queue2, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");
}
运行结果:
任务1,任务5,任务2,任务3,任务4
创建了新的队列,所以是不会造成当前队列的阻塞的。
四、总结
- 同步执行(sync) :无新开线程能力,阻塞当前线程,任务严格顺序执行;同一串行队列嵌套同步任务,必然触发死锁
- 异步执行(async):具备新开线程能力,不阻塞当前线程;在并发队列中任务无序并行执行,在串行队列中任务有序排队执行
- 主队列特殊规则:无论同步还是异步执行,均不会开启新线程;同步执行会死锁,异步执行仅延迟执行、不崩溃
- 队列嵌套核心:同一串行队列嵌套同步任务必死锁;不同队列、并发队列嵌套,均不会触发死锁