为何Swift读写器模式代码出现死锁?
我在Swift中采用带barrier的并发DispatchQueue实现了经典读写器问题解决方案,但运行测试代码时出现死锁。以下是我期望线程安全的数据结构:
class Container<T> { private var _value: T private let _queue = DispatchQueue(label: "containerQueue", attributes: .concurrent) init(_ value: T) { self._value = value } var value: T { get { _queue.sync { _value } } set { _queue.async(flags: .barrier) { self._value = newValue } } } }
测试代码如下:
class ContainerTest { let testQueue = DispatchQueue(label: "testQueue", attributes: .concurrent) var container = Container(0) let group = DispatchGroup() func runTest() { for i in 0..<1000 { testQueue.async(group: group) { self.container.value = max(i, self.container.value) } } group.notify(queue: .main) { print("Finished") } } }
这段重复执行的代码仅为随机读写操作,用于压力测试该数据结构。运行时“Finished”从未打印,但将_queue.async(flags: .barrier)改为_queue.sync(flags: .barrier)后,“Finished”可正常打印。我猜测使用async写入时出现死锁,但这是常用的教科书级方案,请问是代码本身还是测试代码存在问题?原因是什么?
你遇到的不是死锁,而是任务生命周期不匹配和测试环境的主线程退出时机问题:
核心问题
DispatchGroup的完成判定提前
testQueue的异步任务中,container.value = ...的写入是通过_queue.async(flags: .barrier)异步执行的——这意味着testQueue的任务在发起写入请求后会立刻结束,DispatchGroup会认为所有任务都已完成并触发notify。但此时Container的实际写入操作可能还在_queue中排队执行,若程序主线程已经退出(比如命令行工具、Playground等场景),main队列上的notify闭包根本没机会执行,所以看不到“Finished”输出。同步写入的差异
改成_queue.sync(flags: .barrier)后,testQueue的任务必须等待Container的写入操作完成才会结束,这会大幅延长DispatchGroup的等待时间,主线程有足够的时间存活到notify闭包执行,因此能正常打印“Finished”。
代码修正建议
1. 调整Container的写入逻辑
异步写入适合不需要等待写入完成的场景,但如果调用方需要感知写入结果,应该提供同步写入选项或异步回调:
class Container<T> { private var _value: T private let _queue = DispatchQueue(label: "containerQueue", attributes: .concurrent) init(_ value: T) { self._value = value } var value: T { get { _queue.sync { _value } } set { // 保留异步写入,供无需等待的场景 _queue.async(flags: .barrier) { self._value = newValue } } } // 新增同步写入方法,供需要等待写入完成的场景使用 func setValue(_ newValue: T, waitUntilDone: Bool = false) { if waitUntilDone { _queue.sync(flags: .barrier) { self._value = newValue } } else { _queue.async(flags: .barrier) { self._value = newValue } } } }
2. 修正测试代码
确保主线程等待DispatchGroup完成,或者调整任务的group管理逻辑:
class ContainerTest { let testQueue = DispatchQueue(label: "testQueue", attributes: .concurrent) var container = Container(0) let group = DispatchGroup() func runTest() { for i in 0..<1000 { testQueue.async(group: group) { // 使用同步写入,确保任务完成时写入已结束 self.container.setValue(max(i, self.container.value), waitUntilDone: true) } } // 让主线程等待group完成(适用于命令行环境) group.wait() group.notify(queue: .main) { print("Finished") print("Final value: \(self.container.value)") } } }
总结
- 原代码没有死锁,只是异步写入导致
DispatchGroup的任务完成判定与实际写入操作不同步,加上主线程提前退出导致notify闭包未执行。 - 异步写入是读写器模式的合理实现,但要根据场景选择:无需等待写入结果时用异步,需要保证写入完成后再执行后续逻辑时用同步。
内容的提问来源于stack exchange,提问作者Alex

