如何借助semaphore/lock解决数据竞争/读写问题?

How to solve data race/ read and write problem with a help of semaphore/lock?

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

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

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
    }
}
  1. 是的,你可以用信号量解决你的问题。它还用于访问共享资源。
  2. 并行读取没有问题,但是如果你想实现单写,那么你需要小心并妥善处理。 (即使你有并行单写+单读)

你问过:

Is it possible to solve read and write problem with a help of semaphore or lock?

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

It is possible to make the solution having serial write and serial read but is it possible to have concurrent reads (giving the possibility to have concurrent reads at one time)?

那就更复杂了。信号量和锁有助于简单的同步,禁止 any 并发访问(包括禁止并发读取)。

允许并发读取的方法称为“reader-writer”模式。但是 semaphores/locks 在不添加各种状态属性的情况下不会自然地适应 reader-writer 模式。我们通常使用并发 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 reader-writer 模式虽然比信号量模式更有效,但仍然不如简单的锁方法快(即使后者不支持并发读取) . GCD 开销超过并发读取和异步写入所带来的好处。

但是在您的用例中对各种模式进行基准测试,看看哪种模式最适合您。参见