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)?
Here is my simple implementation but reads are not concurrent.
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
}
}
You asked:
Is it possible to solve read and write problem with a help of semaphore or lock?
Yes. The approach that you have supplied should accomplish that.
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)?
That’s more complicated. Semaphores and locks lend themselves to simple synchronization that prohibits any concurrent access (including prohibiting concurrent reads).
The approach which allows concurrent reads is called the “reader-writer” pattern. But semaphores/locks do not naturally lend themselves to the reader-writer pattern without adding various state properties. We generally accomplish it with concurrent GCD queue, performing reads concurrently, but performing writes with a barrier (to prevent any concurrent operations):
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
}
}
A few observations:
Semaphores are relatively inefficient. In my benchmarks, a simple NSLock
is much faster, and an unfair lock even more so.
The GCD reader-writer pattern, while more efficient than the semaphore pattern, is still not as quick as a simple lock approaches (even though the latter does not support concurrent reads). The GCD overhead outweighs the benefits achieved by concurrent reads and asynchronous writes.
But benchmark the various patterns in your use case and see which is best for you. See https://stackoverflow.com/a/58211849/1271826.