1-27.【GCD】什么是死锁?GCD 中最经典的死锁场景?

3 阅读2分钟

1️⃣ 什么是死锁

死锁是指两个或多个任务互相等待对方释放资源或完成操作,从而导致所有相关任务永远无法继续执行的状态。

核心特点:

  1. 任务 A 等待资源/任务 B 完成
  2. 任务 B 等待资源/任务 A 完成
  3. 结果:双方都阻塞 → 系统或队列停滞

例子(抽象)

任务 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. 任务 1 被派发到 serialQueue 执行

  2. 任务 1 运行中调用 serialQueue.sync { 任务 2 }

  3. sync 要求 等待任务 2 执行完毕

  4. serialQueue串行队列,当前线程正在执行 任务 1,无法开始 任务 2

  5. 结果:

    • 任务 1 等待 任务 2 完成
    • 任务 2 等待串行队列空闲
      → 双方互相等待 → 死锁

✅ 关键点:串行队列 + sync 调用自己


2.2 其他典型死锁场景

  1. 主队列 sync 死锁
DispatchQueue.main.sync {
    print("在主线程同步调度自己")
}
  • 在主线程调用 DispatchQueue.main.sync → 阻塞主线程
  • 回调 block 需要在主线程执行
  • 结果:主线程永远被阻塞 → 死锁
  1. 资源互相依赖
  • 多个串行队列或任务依赖彼此完成,使用 sync 调度 → 可能互相等待

2.3 异步不会死锁

serialQueue.async {
    serialQueue.async {
        print("不会死锁")
    }
}
  • 因为 async 不会阻塞调用线程
  • 内部 block 排队等待串行队列执行 → 顺序安全,不阻塞

3️⃣ GCD 死锁总结

场景机制是否死锁
串行队列内 sync 调用自己任务等待串行队列执行下一个 block✅ 死锁
主队列 sync 调用自己主线程阻塞等待主队列 block 执行✅ 死锁
async 调用自己或同队列异步排队❌ 安全
并发队列 sync 调用自己可执行,但可能阻塞线程⚠️ 注意线程耗尽

4️⃣ 避免死锁的原则

  1. 不要在串行队列内 sync 调用自己
  2. 主线程不要 sync 调用主队列
  3. 长耗时任务在后台并发队列执行
  4. async 替代 sync:串行队列内部 async 排队不会死锁

💡 一句话总结

GCD 死锁最经典场景就是:在串行队列(包括主队列)内部同步提交自己,造成任务互相等待而永远无法完成。