I’m looking for a library that implements async/await-friendly read-write lock. I.e., the one which stores continuations instead of blocking threads. Any recommendations?
Do custom executors allow this?
I’m looking for a library that implements async/await-friendly read-write lock. I.e., the one which stores continuations instead of blocking threads. Any recommendations?
Do custom executors allow this?
Have you seen @gwendal.roue’s Semaphore?
Thanks, that will be useful as a building block or an inspiration.
Anything similar, but with a ready-to-use read-write lock?
Apologies for the late reply, I am really busy these days. I assume you usually have to write your own. There are a lot of factors that affect the design. For example, whether a FIFO order is important or whether it is write heavy or read heavy or how contested and performance critical it is going to be.
import Atomics
/// A simple container class that uses *multiple readers / single writer* concurrency pattern to protect the value stored in it.
///
/// Obviously, the instances of this class are `Sendable`.
///
public final class ReadersWriter<Value>: @unchecked Sendable {
// The storage for the protected value
private var _value: Value
// Atomic counter to implement Readers/Writer Lock
private let readerCount = ManagedAtomic(0)
// Flag for writing state
private let WRITING = -1
// Add a reader
private func beginReading() async {
var done = false
var count = 0
while true {
count = readerCount.load(ordering: .relaxed)
if count != WRITING {
(done,count) = readerCount.weakCompareExchange(expected: count, desired: count+1, ordering: .acquiringAndReleasing)
if done { break }
}
await Task.yield()
}
}
// Remove a reader
private func doneReading() { readerCount.wrappingDecrement(ordering: .acquiringAndReleasing) }
// Enter writing state:
private func signalWriting() async {
while true {
let (done,_) = readerCount.weakCompareExchange(expected: 0, desired: WRITING, ordering: .acquiringAndReleasing)
if done { break }
await Task.yield()
}
}
// Leave writing state
private func doneWriting() { readerCount.store(0, ordering: .releasing) }
// The protected value as an async read only property
public var value: Value {
get async {
await beginReading()
defer { doneReading() }
return _value
}
}
/// The async method to set the value.
///
/// Once we get async `set` feature for properties, it will become the setter of the `value` property.
///
/// - Parameters:
/// - value: The new value to set
/// - Returns: The old value that was overwritten
///
@discardableResult
public func set(to value: Value) async -> Value {
await signalWriting()
let oldValue = _value
_value = value
doneWriting()
return oldValue
}
/// An async method to get the current value and transform it to the new value.
///
/// Rethrows the error thrown by `transform` closure.
///
/// - Parameters:
/// - transform: A potentilly async and throwing closure that receives the current value and return the new value.
///
public func update(_ transform: (Value) async throws -> Value ) async rethrows {
await signalWriting()
defer { doneWriting() }
_value = try await transform(_value)
}
/// An async method to mutate the value in-place
///
/// Rethrows the error thrown by `transform` closure.
///
/// - Parameters:
/// - transform: A potentilly throwing closure that receives the mutable value to update.
public func mutate(_ transform: (inout Value) throws -> Void ) async rethrows {
await signalWriting()
defer { doneWriting() }
try withUnsafeMutablePointer(to: &_value) { valuePtr in // FIXME!
try transform(&valuePtr.pointee)
}
}
/// Creates a `ReadersWriter` instance.
///
/// - Parameters:
/// - value: The initial value.
///
public init(_ value: Value) { _value = value }
}
I wrote it in an hour and tested it for a couple of minutes, so use it at your own risk.