You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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写入时出现死锁,但这是常用的教科书级方案,请问是代码本身还是测试代码存在问题?原因是什么?

原因分析与解决方案

你遇到的不是死锁,而是任务生命周期不匹配和测试环境的主线程退出时机问题:

核心问题

  1. DispatchGroup的完成判定提前
    testQueue的异步任务中,container.value = ...的写入是通过_queue.async(flags: .barrier)异步执行的——这意味着testQueue的任务在发起写入请求后会立刻结束,DispatchGroup会认为所有任务都已完成并触发notify。但此时Container的实际写入操作可能还在_queue中排队执行,若程序主线程已经退出(比如命令行工具、Playground等场景),main队列上的notify闭包根本没机会执行,所以看不到“Finished”输出。

  2. 同步写入的差异
    改成_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 12:37:24