背景
最近作者在进行多线程问题排查和整理时,发现了好多问题都是由于GCD的使用不规范造成的,因此在这里主要分享GCD的使用方法,希望大家能够在测试时更早发现问题。
GCD简介
GCD 为苹果推出的多核编程解决方案,它不仅能够自动利用多个核心处理数据,还能够自动管理生命周期,不需要程序猿手动管理。在日常的编程中十分常用。其优点如下:
- GCD 可用于多核的并行运算;
- GCD 会自动利用更多的 CPU 内核(比如双核、四核);
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
GCD 任务和队列
学习 GCD 之前,先来了解 GCD 中两个核心概念:『任务』 和 『队列』。
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
- 同步执行(sync):
- 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
- 只能在当前线程中执行任务,不具备开启新线程的能力。
- 异步执行(async):
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
- 可以在新的线程中执行任务,具备开启新线程的能力。
队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。队列的结构可参考下图:
在 GCD 中有两种队列:『串行队列』 和 『并发队列』。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
- 串行队列(Serial Dispatch Queue):
- 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
- 并发队列(Concurrent Dispatch Queue):
- 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
两者具体区别如下两图所示:
GCD 的使用步骤
GCD 的使用步骤其实很简单,只有两步:
- 创建一个队列(串行队列或并发队列);
- 将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。
下边来看看队列的创建方法 / 获取方法,以及任务的创建方法。
1. 创建队列
可以使用 dispatch_queue_create 方法来创建队列。该方法需要传入两个参数:
- 第一个参数表示队列的唯一标识符,用于 DEBUG,可为空。队列的名称推荐使用应用程序 ID 这种逆序全程域名。
- 第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。
// 串行队列的创建方法
2. 任务的创建方法
GCD 提供了同步执行任务的创建方法 dispatch_sync
和异步执行任务创建方法 dispatch_async
。
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
虽然使用 GCD 只需两步,但是既然我们有两种队列(串行队列 / 并发队列),两种任务执行方式(同步执行 / 异步执行),如果当前代码默认放在主队列中,我们也有两种特殊的组合方式。于是我们就有了六种不同的组合方式。这四种不同的组合方式是:
同步执行 并发队列 异步执行 并发队列 同步执行 串行队列 异步执行 串行队列 同步执行 主队列 异步执行 主队列
我们先来考虑最基本的使用,也就是当前线程为 『主线程』 的环境下,『不同队列』 『不同任务』 简单组合使用的不同区别。暂时不考虑 『队列中嵌套队列』 的这种复杂情况。
『主线程』中,『不同队列』 『不同任务』简单组合的区别:
任务 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步(sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 死锁卡住不执行 |
异步(async) | 有开启新线程,串行执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程,串行执行任务 |
总结
GCD的使用看起来非常简单,但是在实际使用当中必须要严格遵循队列和任务的关系,以及内部数据的线程安全,防止出现概率崩溃及锁死的情况。GCD虽然非常好用,但也意味着其产生问题时会造成更大的影响,希望大家在测试过程中重点关注。