学习目标
- 理解并发编程的基本概念和重要性。
- 掌握
Grand Central Dispatch (GCD)
的基本用法,包括串行队列、并行队列、主队列和全局队列。 - 学习如何使用
DispatchGroup
和DispatchSemaphore
进行任务同步。 - 了解
Operation
和OperationQueue
的使用,以及它们与GCD
的区别。 - 掌握如何选择合适的并发技术。
学习内容
1. 并发编程简介
并发编程是指在同一时间段内处理多个任务的能力。在移动应用开发中,并发通常用于在后台执行耗时操作(如网络请求、数据处理),以避免阻塞主线程,从而保持用户界面的响应性。
2. Grand Central Dispatch (GCD)
GCD
是苹果提供的一套基于 C 语言的底层并发编程 API,它通过管理调度队列来执行任务。
2.1 调度队列 (Dispatch Queues)
- 串行队列 (Serial Queues):任务按顺序一个接一个地执行。你可以创建任意数量的串行队列,每个队列中的任务都是串行执行的,但不同串行队列之间的任务可以并行执行。
let serialQueue = DispatchQueue(label: "com.example.serialQueue") serialQueue.async { // 异步执行 print("Task 1 on serial queue") } serialQueue.async { // 异步执行 print("Task 2 on serial queue") } // 输出顺序不确定,但 Task 1 和 Task 2 在 serialQueue 上是串行执行的
- 并行队列 (Concurrent Queues):任务可以并行执行。任务的开始顺序与添加顺序一致,但完成顺序不确定。
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent) concurrentQueue.async { // 异步执行 for i in 0..<5 { print("Task A: \(i)") } } concurrentQueue.async { // 异步执行 for i in 0..<5 { print("Task B: \(i)") } } // Task A 和 Task B 会并行执行,输出交错
- 主队列 (Main Queue):一个特殊的串行队列,用于执行 UI 更新和所有与用户交互相关的任务。所有 UI 操作都必须在主队列上执行。
DispatchQueue.main.async { // 异步执行,回到主线程更新 UI // Update UI here print("Updating UI on main thread") }
- 全局队列 (Global Queues):系统提供的并行队列,有不同的优先级(
qos
:quality of service
)。let globalQueue = DispatchQueue.global(qos: .userInitiated) globalQueue.async { // 异步执行 print("Performing a user-initiated task") }
2.2 同步与异步
async
:异步执行,立即返回,不阻塞当前线程。sync
:同步执行,阻塞当前线程,直到任务完成。- 警告:在主队列上同步执行任务会导致死锁。
2.3 延迟执行
asyncAfter
:在指定时间后执行任务。DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { // 2秒后执行 print("This message appears after 2 seconds") }
2.4 调度组 (DispatchGroup)
用于在多个任务完成后得到通知。
- 示例:
let group = DispatchGroup() let queue = DispatchQueue.global() queue.async(group: group) { // 进入组 print("Task 1 started") Thread.sleep(forTimeInterval: 1.0) print("Task 1 finished") } queue.async(group: group) { // 进入组 print("Task 2 started") Thread.sleep(forTimeInterval: 2.0) print("Task 2 finished") } group.notify(queue: .main) { // 组内所有任务完成后,在主队列通知 print("All tasks in group finished!") } print("Main thread continues...")
2.5 调度信号量 (DispatchSemaphore)
用于控制对共享资源的访问,或限制并发任务的数量。
- 示例:限制并发任务为 2 个
let semaphore = DispatchSemaphore(value: 2) // 允许同时有2个任务访问资源 let queue = DispatchQueue.global() for i in 1...5 { queue.async { semaphore.wait() // 信号量减1,如果为负则等待 print("Task \(i) started") Thread.sleep(forTimeInterval: Double.random(in: 0.5...2.0)) print("Task \(i) finished") semaphore.signal() // 信号量加1 } }
3. Operation 和 OperationQueue
Operation
和 OperationQueue
是基于 GCD
构建的更高层的抽象,提供了更多的功能,如操作依赖、取消、暂停、恢复等。
3.1 Operation
-
一个抽象类,代表一个单一的、可执行的任务。
-
你可以创建
BlockOperation
(执行一个或多个闭包)或Custom Operation
(继承Operation
并重写main()
方法)。 -
示例:
BlockOperation
let operation1 = BlockOperation { print("Operation 1") } let operation2 = BlockOperation { print("Operation 2") } operation1.start() // 直接启动操作 operation2.start()
3.2 OperationQueue
-
一个队列,用于管理和调度
Operation
的执行。 -
可以设置最大并发操作数。
-
主操作队列:
OperationQueue.main
,在主线程上执行操作。 -
示例:
let queue = OperationQueue() queue.maxConcurrentOperationCount = 3 // 最大并发数 let op1 = BlockOperation { print("Op 1") } let op2 = BlockOperation { print("Op 2") } let op3 = BlockOperation { print("Op 3") } let op4 = BlockOperation { print("Op 4") } queue.addOperation(op1) queue.addOperation(op2) queue.addOperation(op3) queue.addOperation(op4) // 添加依赖:op4 在 op1 和 op2 完成后才执行 op4.addDependency(op1) op4.addDependency(op2) // 取消所有操作 // queue.cancelAllOperations()
4. GCD vs OperationQueue
特性 | GCD | OperationQueue |
---|---|---|
抽象级别 | 低层级,基于 C 语言 | 高层级,基于 GCD 构建 |
任务表示 | 闭包 (Blocks) | Operation 对象 |
任务依赖 | 需要手动管理 (如 DispatchGroup ) | 内置支持 (addDependency ) |
任务取消 | 不直接支持 | 内置支持 (cancel() ) |
任务状态 | 无 | 可观察 (isReady, isExecuting, isFinished, isCancelled) |
任务暂停/恢复 | 不直接支持 | 内置支持 (isSuspended ) |
适用场景 | 简单、轻量级任务,对性能要求高 | 复杂任务,需要更多控制和管理 |
5. 如何选择
- 使用 GCD:
- 当你需要执行简单的、一次性的任务时。
- 当你需要对队列和任务的调度有更细粒度的控制时。
- 当你需要使用
DispatchGroup
或DispatchSemaphore
进行任务同步时。
- 使用 OperationQueue:
- 当你需要管理复杂的任务依赖关系时。
- 当你需要取消、暂停或恢复任务时。
- 当你需要对任务的执行状态进行观察时。
- 当你需要限制并发操作的数量时。
实践练习
- GCD 队列:
- 创建一个串行队列,异步执行两个任务,观察它们的执行顺序。
- 创建一个并行队列,异步执行两个任务,观察它们的执行顺序。
- 在一个后台队列中执行一个耗时操作,然后回到主队列更新 UI(打印一条消息)。
- DispatchGroup:
- 模拟两个网络请求,使用
DispatchGroup
在所有请求完成后打印一条消息。
- 模拟两个网络请求,使用
- DispatchSemaphore:
- 模拟一个资源池,限制同时只有3个任务可以访问该资源。
- OperationQueue:
- 创建两个
BlockOperation
,让第二个操作依赖于第一个操作。 - 将它们添加到
OperationQueue
中执行,观察执行顺序。 - 尝试设置
maxConcurrentOperationCount
来控制并发数。
- 创建两个
思考题
- 为什么 UI 操作必须在主队列上执行?
sync
和async
在不同队列上的行为有何不同?GCD
和OperationQueue
在实际项目中有哪些典型的应用场景?