这是我参与「第四届青训营 」笔记创作活动的第9天
1. 多线程编程实现方案对比
| 技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
|---|---|---|---|---|
| pthread | 类UNIX系统通用的多线程API,跨平台/可移植,使用难度大 | C | 程序员管理 | 几乎不用 |
| NSThread | 使用更加面向对象,简单易用,可直接操作线程对象 | OC | 程序员管理 | 偶尔使用 |
| GCD | 旨在代替NSThread等线程技术,充分利用设备的多核 | C | 自动管理 | 经常使用 |
| NSOperation | 基于GCD,比GCD多一些更简单实用的功能,使用更加面向对象 | OC | 自动管理 | 经常使用 |
2. 为什么使用 GCD
Grand Central Dispatch( GCD ) ,是 Apple 提供的异步执行任务的技术之一。线程管理用的代码由 GCD 在系统级实现,让开发者可以将注意力集中在任务的编写上。GCD 抽象出了任务、队列 等概念,将并发编程的范式变为了定义想执行的任务并追加到适当的派发队列
使用 GCD 实现多线程编程的好处:
- 自动使用更多 CPU 内核
- 自动管理线程的生命周期
- 提供了易于使用的并发模型
- 编码更加简洁,无需多余的线程管理任务
3. GCD 接口介绍
任务与队列
任务:即在线程中执行的代码,在 GCD API 中以 block 的形式提交到队列
队列:任务派发队列(先进先出FIFO,First-In-First-Out),任务追加到派发队列后按照先进先出的次序派发到对应线程进行处理
串行队列和并发队列
串行队列:队列中的任务在单个线程中顺序执行,执行中的任务结束后才能继续执行下一个任务
并发队列:队列中的任务在异步执行的情况下可以分发到多个线程下同时执行
获取队列的方式
- 通过
dispatch_queue_create创建自定义的队列,通过传入参数DISPATCH_QUEUE_SERIAL、DISPATCH_QUEUE_CONCURRENT来区分串行队列和并行队列 - 系统标准提供了一个主队列(串行)和四个全局队列(并行,有优先级差异),可以通过
dispatch_get_main_queue、dispatch_get_global_queue分别获取主队列和全局队列
同步执行与异步执行
同步执行(dispatch_sync):
- 提交任务到指定队列,在该任务执行结束之前会一直等待
- 提交的任务只能在当前线程执行,不具备开启线程的能力
异步执行(dispatch_async):
- 提交任务到指定队列,继续往下执行不等待任务执行结束
- 可以在新的线程中执行任务,具备开启线程的能力
dispatch_sync 和 dispatch_async 的传入参数是队列,同步执行、异步执行和串行队列、并发队列的组合调用分别是什么结果?
由于主队列和主线程具有一定的特殊性,以下分析当前线程为主线程的情况下,同步执行、异步执行与并发队列、串行队列、主队列的组合调用情况:
| 并行队列 | 串行队列(非主队列) | 主队列 | |
|---|---|---|---|
| 同步执行 (dispatch_sync) | 不开启新线程,在当前线程串行执行任务 | 不开启新线程,在当前线程串行执行任务 | 发生死锁 |
| 异步执行 (dispatch_async) | 开启新线程,并行执行队列中的任务 | 开启一条新线程,串行执行队列中的任务 | 不开启新线程,在当前线程的下一个 runloop 执行任务 |
dispatch_after
使用 dispatch_after可以实现延时执行任务的效果,需要注意的是任务会在指定延时后提交到队列,而任务真正的执行的时间点是未知的。在需要大致延时的情况下 dispatch_after 还是比较有效的
dispatch_group
如果希望在一个 Dispatch Queue 中所有任务执行完或者多个 Dispatch Queue 中的所有任务执行完后再执行某任务,可以通过dispatch_group、dispatch_group_notify 实现
使用 dispatch_group_wait 可以设置等待 group 执行的时间上限,当 group 中全部任务执行完或者满足 timeout 条件 dispatch_group_wait 才返回,可通过返回值区分两种返回类型
dispatch_apply
GCD 提供了 dispatch_apply 接口用于实现快速迭代,dispatch_apply 将按照指定的次数将指定的任务追加到派发队列,并等待队列中全部任务执行结束
dispatch_apply 和 dispatch_sync 函数类似,会等待队列中的任务执行结束,在主线程中使用可能引起卡顿问题或者发生死锁,尽量在 dispatch_async 函数中非同步地执行 dispatch_apply
dispatch_once
某个操作在应用程序生命周期中只执行一次,这种需求很常见,比如单例模式。dispatch_once 函数可以保证指定的处理在应用程序生命周期中只被执行一次