Created
September 17, 2016 15:24
-
-
Save gcbrueckmann/3dcad5ff56a499fcf5a4892bf683f3e1 to your computer and use it in GitHub Desktop.
Coordinate concurrent access to shared resources
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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