深入理解 DispatchQueue.sync 的死锁陷阱:原理、案例与最佳实践

为什么要谈"死锁"

在 Swift 并发编程中,DispatchQueue.sync 以"阻塞式同步"著称:简单、直观、线程安全,却也最容易让生产环境直接崩溃。

什么是死锁(Deadlock)

维度 说明
定义 两个(或多个)执行单元互相等待对方释放资源,导致永远阻塞。
在 GCD 中的表现 线程 A 通过 sync 提交任务到队列 Q,而队列 Q 正在等待线程 A 完成 → 循环等待 → 触发 EXC_BAD_INSTRUCTION 崩溃。
常见结果 主线程卡死、App 秒退;Crash 日志中出现 0x8badf00d(应用无响应)或 EXC_I386_INVOP(非法操作)等错误码。

餐厅比喻

  • waiter(服务员)同步下订单给 chef(厨师);
  • chef 需要 waiter 回去问顾客口味,又同步派任务给 waiter;
  • 两人互相等,餐厅停摆 → 死锁。

Swift 最小死锁示例

swift 复制代码
import Foundation

// 1. 同队列嵌套 sync → 立即崩溃
let queue = DispatchQueue(label: "com.demo.queue")
queue.sync {
    print("外层 sync")
    queue.sync {          // ❌ 在这里死锁
        print("永远进不来")
    }
}

运行后控制台只会打印 外层 sync,随后 App 崩溃。

原因:

  • 外层闭包已占用队列唯一线程;
  • 内层 sync 要求同一条线程再次进入 → 无法满足 → 死锁。

双队列交叉死锁(更接近真实业务)

swift 复制代码
let waiter = DispatchQueue(label: "waiter")
let chef   = DispatchQueue(label: "chef")

// 模拟下单流程
waiter.sync {
    print("① Waiter:同步下单给 Chef")
    
    chef.sync {
        print("② Chef:同步要求 Waiter 去问口味")
        
        waiter.sync {     // ❌ 交叉等待
            print("③ Waiter:永远无法执行")
        }
    }
}

崩溃点:③ 处 waiter 队列已被①占用,而①又在等② → 循环等待。

如何"一键"解决------把任意一个 sync 改成 async

修改方案 代码片段 是否死锁
① → async waiter.async { ... }
② → async chef.async { ... }
③ → async waiter.async { ... }

结论: 只要打破"循环等待链"中的任意一个环,死锁即刻解除。

在真实项目中,优先把"反向调用"做成 async 即可。

工程中最容易踩的"隐性死锁"

  1. 对外暴露 sync 接口
swift 复制代码
class ImageCache {
    private let queue = DispatchQueue(label: "cache")
    private var storage: [String: UIImage] = [:]
    
    // ❌ 危险:把内部队列 sync 暴露给外部
    func read<T>(_ closure: () -> T) -> T {
        return queue.sync(execute: closure)   // 闭包里可能再调 read()
    }
}

问题:调用方可能在闭包里再次调用 read() → 递归同步 → 死锁。

解决:

  • 绝不对外暴露 sync;
  1. 主线程 sync 到主队列
swift 复制代码
DispatchQueue.main.sync {   // ❌ 100 % 死锁
    // 代码永远不会进来
}

场景:在后台线程计算完后,想"立刻"回主线程刷新 UI,却手滑写成 sync

正确姿势:

永远用 DispatchQueue.main.async { ... }

sync 的正确打开方式------"私有队列 + 原子访问"

swift 复制代码
/// 线程安全的日期格式化器缓存
final class DateFormatterCache {
    private var formatters: [String: DateFormatter] = [:]
    private let queue = DispatchQueue(label: "cache.\(UUID().uuidString)")
    
    func formatter(using format: String) -> DateFormatter {
        // 1. 只在此私有队列里同步,外部无法递归根除
        return queue.sync { [unowned self] in
            if let cached = formatters[format] {
                return cached
            }
            let df = DateFormatter()
            df.locale = Locale(identifier: "en_US_POSIX")
            df.dateFormat = format
            formatters[format] = df
            return df
        }
    }
}

为什么这里不会死锁?

  • queue 私有,外部无法直接往它塞 sync 任务;
  • 函数内部无递归调用;
  • 闭包执行时间极短,不会阻塞用户可见线程。

checklist ✅

使用 sync 前自问 回答
队列是否私有?
闭包里还会 sync 到同队列吗?
阻塞是否影响主线程/用户滑动?

封装一个"防死锁"的读写锁

swift 复制代码
/// 读写锁:写操作 barrier,读操作并发
final class RWLock<T> {
    private var value: T
    private let queue: DispatchQueue
    
    init(_ initial: T) {
        value = initial
        queue = DispatchQueue(label: "rw.\(UUID().uuidString)", attributes: .concurrent)
    }
    
    // 读:并发
    func read<U>(_ closure: (T) throws -> U) rethrows -> U {
        try queue.sync { try closure(value) }
    }
    
    // 写:barrier
    func write(_ closure: @escaping (inout T) -> Void) {
        queue.async(flags: .barrier) { closure(&self.value) }
    }
}

优点:

  • 读并行、写串行;
  • 外部无法拿到 queue 引用,彻底杜绝递归 sync;
  • 所有写操作是 async,不会阻塞调用方。

总结------一句话记住

除非你在做原子访问,且队列私有、无递归,否则一律用 async。

扩展阅读 & 下一步

  1. 官方文档:DispatchQueue.sync
  2. WWDC 2022 -- Visualize and eliminate hangs with Instruments
  3. Swift Concurrency 时代:
    • actor 替代"私有队列 + sync";
    • AsyncSequence 做"异步回调链",天然避免死锁。

学习资料

  1. www.donnywals.com/understandi...
相关推荐
报错小能手10 小时前
ios开发方向——swift错误处理:do/try/catch、Result、throws
开发语言·学习·ios·swift
小夏子_riotous12 小时前
openstack的使用——5. Swift服务的基本使用
linux·运维·开发语言·分布式·云计算·openstack·swift
mCell17 小时前
MacOS 下实现 AI 操控电脑(Computer Use)的思考
macos·agent·swift
用户794572239541317 小时前
【DGCharts】iOS 图表渲染事实标准——8 种图表类型、高度可定制,3 行代码画出一条折线
swiftui·swift
chaoguo12341 天前
Any metadata 的内存布局
swift·metadata·value witness table
tangweiguo030519872 天前
SwiftUI布局完全指南:从入门到精通
ios·swift
用户79457223954133 天前
【RxSwift】Swift 版 ReactiveX,响应式编程优雅处理异步事件流
swift·rxswift
战族狼魂3 天前
XCode 发起视频 和 收到视频通话邀请实现双语功能 中文和俄语
swift
UXbot3 天前
2026年AI全链路产品开发工具对比:5款从创意到上线一站式平台深度解析
前端·ui·kotlin·软件构建·swift·原型模式
报错小能手3 天前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift