1️⃣ 什么是死锁
死锁是指两个或多个任务互相等待对方释放资源或完成操作,从而导致所有相关任务永远无法继续执行的状态。
核心特点:
- 任务 A 等待资源/任务 B 完成
- 任务 B 等待资源/任务 A 完成
- 结果:双方都阻塞 → 系统或队列停滞
例子(抽象)
任务 A: 需要锁 X, 正在等待锁 Y
任务 B: 需要锁 Y, 正在等待锁 X
→ 双方都阻塞,形成死锁
2️⃣ GCD 中死锁的核心场景
GCD 的死锁通常发生在 串行队列 + sync 调度 下。
2.1 经典场景:串行队列内同步调用自己
let serialQueue = DispatchQueue(label: "serialQueue")
serialQueue.async {
print("任务 1 开始")
// 在同一个串行队列中同步提交任务
serialQueue.sync {
print("任务 2")
}
print("任务 1 结束")
}
分析
-
任务 1被派发到serialQueue执行 -
任务 1运行中调用serialQueue.sync { 任务 2 } -
sync要求 等待任务 2 执行完毕 -
但
serialQueue是 串行队列,当前线程正在执行任务 1,无法开始任务 2 -
结果:
任务 1等待任务 2完成任务 2等待串行队列空闲
→ 双方互相等待 → 死锁
✅ 关键点:串行队列 + sync 调用自己
2.2 其他典型死锁场景
- 主队列 sync 死锁
DispatchQueue.main.sync {
print("在主线程同步调度自己")
}
- 在主线程调用
DispatchQueue.main.sync→ 阻塞主线程 - 回调 block 需要在主线程执行
- 结果:主线程永远被阻塞 → 死锁
- 资源互相依赖
- 多个串行队列或任务依赖彼此完成,使用
sync调度 → 可能互相等待
2.3 异步不会死锁
serialQueue.async {
serialQueue.async {
print("不会死锁")
}
}
- 因为 async 不会阻塞调用线程
- 内部 block 排队等待串行队列执行 → 顺序安全,不阻塞
3️⃣ GCD 死锁总结
| 场景 | 机制 | 是否死锁 |
|---|---|---|
| 串行队列内 sync 调用自己 | 任务等待串行队列执行下一个 block | ✅ 死锁 |
| 主队列 sync 调用自己 | 主线程阻塞等待主队列 block 执行 | ✅ 死锁 |
| async 调用自己或同队列 | 异步排队 | ❌ 安全 |
| 并发队列 sync 调用自己 | 可执行,但可能阻塞线程 | ⚠️ 注意线程耗尽 |
4️⃣ 避免死锁的原则
- 不要在串行队列内 sync 调用自己
- 主线程不要 sync 调用主队列
- 长耗时任务在后台并发队列执行
- async 替代 sync:串行队列内部 async 排队不会死锁
💡 一句话总结:
GCD 死锁最经典场景就是:在串行队列(包括主队列)内部同步提交自己,造成任务互相等待而永远无法完成。