Skip to content

Instantly share code, notes, and snippets.

@gcbrueckmann
Created September 17, 2016 15:24
Show Gist options
  • Save gcbrueckmann/3dcad5ff56a499fcf5a4892bf683f3e1 to your computer and use it in GitHub Desktop.
Save gcbrueckmann/3dcad5ff56a499fcf5a4892bf683f3e1 to your computer and use it in GitHub Desktop.
Coordinate concurrent access to shared resources
/// Coordinates access to a critical resources, allowing an arbitrary number of concurrent read operations that are mutually exclusive with single concurrent write operations.
///
/// This approach is similar to `dispatch_async()` and `dispatch_barrier_async()`, except access blocks are asynchronous, i.e. the barrier is only released once the access block calls its completion handler.
final class AccessCoordinator {
private let queue = DispatchQueue(label: "AccessCoordinator")
private let lock = NSRecursiveLock()
enum AccessType {
/// Like `dispatch_async()`, but fully asynchronous.
/// Waits until all previous exclusive accessors are finished.
/// Allows an arbitrary number of concurrent accessors with the same access type.
case nonexclusive
/// Like `dispatch_barrier_async()`, but fully asynchronous.
/// Waits until all previous exclusive and nonexclusive accessors are finished,
/// then blocks all subsequent exclusive and nonexclusive accessors until finished.
case exclusive
}
typealias AccessorBlock = (_ completionHandler: @escaping () -> Void) -> Void
private struct Accessor {
let accessType: AccessType
let block: AccessorBlock
}
private var accessors = [Accessor]()
private var currentAccessType: AccessType?
private func processAccessorQueue() {
lock.lock()
defer { lock.unlock() }
guard !accessors.isEmpty else { return }
if currentAccessType != .exclusive {
let group = DispatchGroup()
group.notify(queue: queue) {
self.lock.lock()
defer { self.lock.unlock() }
self.currentAccessType = nil
self.queue.async {
self.processAccessorQueue()
}
}
repeat {
guard let accessor = accessors.first else { break }
accessors.remove(at: 0)
currentAccessType = accessor.accessType
group.enter()
queue.async {
accessor.block {
group.leave()
}
}
} while currentAccessType != .exclusive
}
}
/// Requests access to a resource.
///
/// - parameter accessType: The type of access required.
/// - parameter accessorBlock: A block the gets called once access is granted.
/// When you are done accessing the resource, you must call the `completionHandler` passed to this block.
func coordinateAccessWithType(_ accessType: AccessType, byAccessor accessorBlock: @escaping AccessorBlock) {
lock.lock()
defer { lock.unlock() }
accessors.append(Accessor(accessType: accessType, block: accessorBlock))
queue.async {
self.processAccessorQueue()
}
}
}
/// Wraps a resource and manages access to it using an `AccessCoordinator`.
final class ManagedResource<ResourceType> {
private let memory: ResourceType
private let accessCoordinator = AccessCoordinator()
init(_ memory: ResourceType) {
self.memory = memory
}
/// Request immediate and unconditional access to the resource.
///
/// There are very few legitimate use cases for this method.
/// A `ManagedResource` instance blocks access to the underlying memory by default for good reason.
/// One reason to use this method would be to access the resource for identity checks.
func withUnsafeMemory(_ accessorBlock: (ResourceType) -> Void) {
accessorBlock(memory)
}
typealias AccessorBlock<ResourceType> = (ResourceType, () -> Void) -> Void
private func access(withType accessType: AccessCoordinator.AccessType, byAccessor accessorBlock: @escaping AccessorBlock<ResourceType>) {
accessCoordinator.coordinateAccessWithType(accessType) { accessCompletionHandler in
accessorBlock(self.memory) {
accessCompletionHandler()
}
}
}
/// Requests non-exclusive access to the resource.
///
/// - parameter accessorBlock: A block the gets called once access is granted.
/// When you are done accessing the resource, you must call the `completionHandler` passed to this block.
func access(byAccessor accessorBlock: @escaping AccessorBlock<ResourceType>) {
access(withType: .nonexclusive, byAccessor: accessorBlock)
}
/// Requests exclusive access to the resource.
///
/// - parameter accessorBlock: A block the gets called once access is granted.
/// When you are done accessing the resource, you must call the `completionHandler` passed to this block.
func accessExclusively(byAccessor accessorBlock: @escaping AccessorBlock<ResourceType>) {
access(withType: .exclusive, byAccessor: accessorBlock)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment