1️⃣ 核心原则
async/await 不自动保证线程安全,GCD 也只是队列调度工具。
混用时,要明确 任务执行上下文 和 数据访问隔离,否则容易出现竞态条件或 UI 卡顿。
关键点:
- Actor(包括 MainActor)保证内部状态安全
- GCD 队列本身不能保护跨队列共享状态
- async/await 任务可能切换线程
2️⃣ 常见线程安全问题
① 非 Actor 对象跨线程访问
var count = 0
Task {
count += 1 // 可能在某个线程执行
}
DispatchQueue.global().async {
count += 1 // 并发访问 count → 数据竞争
}
count被 async Task 和 GCD 同时修改- 可能发生数据竞争 → 崩溃或不可预测行为
② MainActor 与 GCD.main 混用
@MainActor
var labelText: String = ""
DispatchQueue.global().async {
labelText = "Hello" // ❌ 非主线程直接修改 UI
}
- 违反 MainActor 数据隔离 → 编译器报错(如果是 @MainActor 修饰)
- 若未使用 Actor,则可能在后台线程修改 UI → 崩溃
③ 多线程共享资源未同步
- 对共享数组、字典等在 async Task 和 GCD 中同时访问
- 必须加锁或使用 Actor,否则可能越界、重复或丢失元素
3️⃣ 安全使用策略
① 使用 Actor 或 MainActor 保护状态
actor Counter {
var count = 0
func increment() { count += 1 }
}
let counter = Counter()
Task {
await counter.increment() // 安全
}
DispatchQueue.global().async {
Task {
await counter.increment() // 也安全
}
}
- Actor 内部串行化访问 → 避免竞态
- 跨线程调用使用
await,保证安全
② UI 操作必须回到主线程 / MainActor
Task {
let data = await fetchData()
await MainActor.run {
label.text = data
}
}
- 即使 Task 在后台线程完成数据处理,也要回主线程更新 UI
- 避免 DispatchQueue.main.async 与 async/await 混用造成竞态
③ 共享资源多线程访问
- 使用 Actor 或 DispatchQueue + barrier
Actor 保护示例
actor SafeArray {
private var items: [Int] = []
func append(_ value: Int) { items.append(value) }
func getAll() -> [Int] { items }
}
GCD barrier 示例
let queue = DispatchQueue(label: "com.example.array", attributes: .concurrent)
var array: [Int] = []
queue.async(flags: .barrier) {
array.append(1)
}
④ 避免在 CPU 密集循环中阻塞主线程
@MainActor
func heavyWork() {
for i in 0..<1_000_000 {
// ❌ CPU 密集,阻塞主线程
}
}
- async Task 或 GCD.global() 执行耗时操作
- 更新 UI → await MainActor.run {}
⑤ 明确 async Task 与 GCD 执行上下文
- Task { } 默认继承父任务上下文 → 不固定线程
- Task.detached { } → 随机线程
- DispatchQueue.async → 指定队列
- 注意不要假设 async Task 永远在主线程执行
4️⃣ 总结建议
- 共享状态 → 用 Actor 或锁保护
- UI 更新 → MainActor.run 或 @MainActor async 函数
- 耗时操作 → 在后台线程执行,不阻塞主线程
- Task 与 GCD 混用 → 明确线程和上下文
- 协作式取消 → 在后台 Task 中定期检查 Task.checkCancellation()
5️⃣ 面试必背结论
- async/await 任务可能在任意线程执行,GCD 调度也可能在不同线程。
- 共享资源必须保证线程安全,否则会发生数据竞争。
- Actor / MainActor 是 async/await 推荐的线程安全隔离机制。
- UI 必须回主线程,耗时操作必须在后台。
- 混用时要明确任务上下文,避免假设线程一致性。