首页 > 解决方案 > 如何借助信号量/锁解决数据竞争/读写问题?

问题描述

是否可以借助信号量或锁来解决读写问题?可以使解决方案具有串行写入和串行读取,但是否可以进行并发读取(有可能同时进行一次读取)?

这是我的简单实现,但读取不是并发的。

class ThreadSafeContainerSemaphore<T> {
    private var value: T
    private let semaphore = DispatchSemaphore(value: 1)
    
    func get() -> T {
        semaphore.wait()
        defer { semaphore.signal() }
        return value
    }
    
    func mutate(_ completion: (inout T) -> Void) {
        semaphore.wait()
        completion(&self.value)
        semaphore.signal()
    }

    init(value: T) {
        self.value = value
    }
}

标签: swiftmultithreadinglockinggrand-central-dispatchsemaphore

解决方案


您问:

是否可以借助信号量或锁来解决读写问题?

是的。您提供的方法应该可以做到这一点。

可以使解决方案具有串行写入和串行读取,但是否可以进行并发读取(有可能同时进行一次读取)?

那更复杂。信号量和锁适用于禁止任何并发访问(包括禁止并发读取)的简单同步。

允许并发读取的方法称为“读写器”模式。但是信号量/锁在不添加各种状态属性的情况下自然不会适用于读写器模式。我们通常使用并发 GCD 队列来完成它,并发执行读取,但使用屏障执行写入(以防止任何并发操作):

class ThreadSafeContainerGCD<Value> {
    private var value: Value
    private let queue = DispatchQueue(label: ..., attributes: .concurrent)

    func get() -> Value {
        queue.sync { value }
    }

    func mutate(_ block: @escaping (inout Value) -> Void) {
        queue.async(flags: .barrier) { block(&self.value) }
    }

    init(value: Value) {
        self.value = value
    }
}

几点观察:

  • 信号量相对低效。在我的基准测试中,简单NSLock的锁要快得多,不公平的锁更是如此。

  • GCD 读写器模式虽然比信号量模式更有效,但仍然不如简单的锁方法快(即使后者不支持并发读取)。GCD 开销超过了并发读取和异步写入所带来的好处。

但是对您的用例中的各种模式进行基准测试,看看哪种模式最适合您。请参阅https://stackoverflow.com/a/58211849/1271826


推荐阅读