Read Write Lock
A read-write lock allows multiple readers or one writer. In Swift code using Grand Central Dispatch, a concurrent queue with barrier writes can provide this behavior for synchronous, short critical sections.
iOS use case
Several background operations read a small cached feature configuration while occasional updates replace an entry.
import Foundation
final class FeatureConfigCache {
private let queue = DispatchQueue(label: "app.feature-config", attributes: .concurrent)
private var values: [String: String] = [:]
func value(for key: String) -> String? {
queue.sync { values[key] }
}
func set(_ value: String, for key: String) {
queue.sync(flags: .barrier) {
values[key] = value
}
}
}
let cache = FeatureConfigCache()
cache.set("enabled", for: "new-home")
let flag = cache.value(for: "new-home")Reads may run together. A barrier write waits for earlier reads and excludes later reads until the mutation finishes.
Production decisions
Never call set from a block already executing on this queue; synchronous dispatch onto the same queue can deadlock. Keep critical sections short and never await inside them. Swift actors are usually easier for asynchronous state, while this approach can suit existing synchronous APIs. Profile contention before replacing a simple serial queue with read-write coordination.
Understand the queue contract
FeatureConfigCache uses a private concurrent dispatch queue. A read enters with queue.sync, so several reads may execute together and the caller receives a value before returning. A write enters with .barrier, which waits for earlier work on this private queue and excludes later reads until the write finishes. The dictionary itself remains private; callers cannot bypass the synchronization boundary.
From example to an iOS service
A feature-flag service can read cached values from background tasks while periodic refreshes replace them. Keep the protected operation short: parse or fetch new configuration outside the queue, then write the finished value in one barrier block. If a whole configuration snapshot changes together, replace one immutable snapshot rather than writing many keys separately. UI code should not block on long critical sections; this design assumes dictionary access is fast.
Failure modes and verification
Do not call value(for:) or set(_:for:) from a block already running on the same queue; synchronous redispatch may deadlock. Test many concurrent readers and writers with Thread Sanitizer, and assert final values under a controlled schedule. A serial queue is simpler and may perform just as well for a small cache. An actor is usually preferable when callers already use async/await; choose read-write coordination only when measurements justify it.