0%

Objective-C基础入门(GCD)

GCD简介

GCD全称Grand Central Dispatch,中央调度中心
它主要用于优化多核处理器和其他对称处理系统
它在一个线程池模式的基础上执行并发的任务

GCD的两大核心概念:任务、队列

任务

任务,即指执行操作,也就是执行的代码,它一般放在block中
在GCD中,执行任务的两大方式:同步执行、异步执行
两者的区别:任务是否立即执行、是否具备开辟新线程的能力

同步执行

同步添加任务到指定队列后,任务不会立即执行,需要等待队列前面的任务执行完成后,再执行此任务
只能在当前队列执行任务,不具备开辟新线程的能力

异步执行

异步添加任务到指定队列后,任务会立即执行
可能会开辟新的线程执行任务,具备开启新线程的能力

队列

队列,即指执行任务的等待队列,它是一种特殊的线性表,遵守FIFO(First in First Out)原则,即队尾插入新任务,队头读取任务
在GCD中,队列的两种类型:并行队列、串行队列

串行队列

每次只执行一个任务,当它执行完毕后,再执行下一个任务(只开启一个线程)

并行队列

可以让多个任务并发(同时)执行(可以开启多个线程)

注意
并发队列的并发功能只有在异步(dispatch_async)方法下才有效。

GCD的使用

GCD的创建分两步:
1、创建一个队列(串行/并行队列)
2、将任务追加到队列中,然后系统根据任务类型执行任务(同步/异步任务)

队列的创建

调用以下方法来创建队列:
dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t _Nullable attr)
第一个参数:队列的唯一标识符,一般用App ID这样的逆序全程域名
第二个参数:队列的类型,DISPATCH_QUEUE_SERIAL是串行队列,DISPATCH_QUEUE_CONCURRENT是并行队列

1
2
3
4
5
// 创建串行队列
dispatch_queue_t queueSerial = dispatch_queue_create(@"io.github.gaoxiangzhang-abner", DISPATCH_QUEUE_SERIAL);

// 创建并行队列
dispatch_queue_t queueConcurrent = dispatch_queue_create(@"io.github.gaoxiangzhang-abner", DISPATCH_QUEUE_CONCURRENT);

默认提供的队列

对于串行队列,GCD默认提供『主队列(Main Dispatch Queue)』

1
2
// 主队列
dispatch_queue_t queueMain = dispatch_get_main_queue();

注意:主队列其实并不特殊,主队列的实质上就是一个普通的串行队列
只是默认情况下,当前代码是放在主队列中的,然后主队列中的代码都会放到主线程中去执行

对于并行队列,GCD默认提供『全局并发队列(Global Dispatch Queue)』
dispatch_get_global_queue: 获取全局列队,
其中第一个参数为优先级,可选的优先级有以下几个,从上到下优先级依次降低:
QOS_CLASS_USER_INTERACTIVE
QOS_CLASS_USER_INITIATED
QOS_CLASS_DEFAULT
QOS_CLASS_UTILITY
QOS_CLASS_BACKGROUND
该函数的第二个参数为系统保留,每次都传入 0 即可。通常我们第一个参数也传入 0 来获取默认的全局列队。

1
2
3
// 全局并发队列
dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);
dispatch_queue_t queueGlobal2 = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);

任务的创建

1
2
3
4
5
6
7
8
9
// 同步执行任务的方法创建
dispatch_sync(queue, ^{
// 执行的代码
});

// 异步执行任务的方法创建
dispatch_async(queue, ^{
// 执行的代码
});

任务和队列的组合

GCD提供了2种队列(串行队列/并行队列),2种任务执行方式(同步执行/异步执行),以及主队列和全局并发队列,其中全局并发队列可以作为普通并发队列使用,而主队列需要研究
因此有6种组合方式:
串行队列 + 同步执行
串行队列 + 异步执行
并行队列 + 同步执行
并行队列 + 异步执行
主队列 + 同步执行
主队列 + 异步执行

我们先来考虑最基本的使用,也就是当前线程为 『主线程』 的环境下,『不同队列』+『不同任务』 简单组合使用的不同区别。暂时不考虑 『队列中嵌套队列』 的这种复杂情况。

『主线程』中,『不同队列』+『不同任务』简单组合的区别:

区别 并发队列 串行队列 主队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁卡住不执行
异步(async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务
注意:从上边可看出: 『主线程』 中调用 『主队列』+『同步执行』 会导致死锁问题。

这是因为在『主线程』的『主队列中追加的同步任务』和『主线程本身的任务』两者之间相互等待,阻塞了『主队列』,最终造成了主队列所在的线程(主线程)死锁问题。
而如果我们在『其他线程』调用『主队列』+『同步执行』,则不会阻塞『主队列』,自然也不会造成死锁问题。最终的结果是:不会开启新线程,串行执行任务。

实际在使用『串行队列』的时候,也可能出现阻塞『串行队列』所在线程的情况发生,从而造成死锁问题。这种情况多见于同一个串行队列的嵌套使用。

比如下面代码这样:在『异步执行』+『串行队列』的任务中,又嵌套了『当前的串行队列』,然后进行『同步执行』

1
2
3
4
5
6
7
8
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 异步执行 + 串行队列
dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
});

执行上面的代码会导致『串行队列中追加的任务』和『串行队列中原有的任务』
两者之间相互等待,阻塞了『串行队列』,最终造成了串行队列所在的线程(子线程)死锁问题。
主队列造成死锁也是基于这个原因,所以,这也进一步说明了主队列其实并不特殊

区别 『异步执行+并发队列』嵌套『同一个并发队列』 『同步执行+并发队列』嵌套『同一个并发队列』 『异步执行+串行队列』嵌套『同一个串行队列』 『同步执行+串行队列』嵌套『同一个串行队列』
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁卡住不执行 死锁卡住不执行
异步(async) 有开启新线程,并发执行任务 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 有开启新线程(1 条),串行执行任务

类比

通过一个类比来理解 队列、任务 以及 线程 之间的关系

假设现在有5个人要穿过一道门禁,这道门禁总共有10个入口
管理员可以决定同一时间打开几个入口,可以决定同一时间让一个人单独通过还是多个人一起通过
不过默认情况下,管理员只开启一个入口,且一个通道一次只能通过一个人

这个故事里,人好比是『任务』,管理员好比是『系统』,入口则代表『线程』

5 个人表示有 5 个任务,10 个入口代表 10 条线程
串行队列 好比是 5 个人排成一支长队
并发队列 好比是 5 个人排成多支队伍,比如 2 队,或者 3 队。
同步任务 好比是管理员只开启了一个入口(当前线程)
异步任务 好比是管理员同时开启了多个入口(当前线程 + 新开的线程)

『异步执行 + 并发队列』 可以理解为:现在管理员开启了多个入口(比如 3 个入口),5 个人排成了多支队伍(比如 3 支队伍),这样这 5 个人就可以 3 个人同时一起穿过门禁了

『同步执行 + 并发队列』 可以理解为:现在管理员只开启了 1 个入口,5 个人排成了多支队伍。虽然这 5 个人排成了多支队伍,但是只开了 1 个入口啊,这 5 个人虽然都想快点过去,但是 1 个入口一次只能过 1 个人,所以大家就只好一个接一个走过去了,表现的结果就是:顺次通过入口

换成 GCD 里的语言就是说:

『异步执行 + 并发队列』就是:系统开启了多个线程(主线程+其他子线程),任务可以多个同时运行
『同步执行 + 并发队列』就是:系统只默认开启了一个主线程,没有开启子线程,虽然任务处于并发队列中,但也只能一个接一个执行了

参考链接:https://www.jianshu.com/p/2d57c72016c6