Created
May 4, 2021 10:26
-
-
Save alexdrone/247eac5ac5ea25fd0a8fa5cd2da06457 to your computer and use it in GitHub Desktop.
Locks and Atomic Property wrappers.
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
import Foundation | |
// MARK: - Locking | |
public protocol Locking { | |
init() | |
/// Attempts to acquire a lock, blocking a thread’s execution until the lock can be acquired. | |
func lock() | |
/// Relinquishes a previously acquired lock. | |
func unlock() | |
} | |
// MARK: - Foundation Locks | |
/// An object that coordinates the operation of multiple threads of execution within the | |
/// same application. | |
extension NSLock: Locking { } | |
/// A lock that may be acquired multiple times by the same thread without causing a deadlock. | |
extension NSRecursiveLock: Locking { } | |
/// A lock that multiple applications on multiple hosts can use to restrict access to some | |
/// shared resource, such as a file. | |
extension NSConditionLock: Locking { } | |
// MARK: - Mutex | |
/// A mechanism that enforces limits on access to a resource when there are many threads | |
/// of execution. | |
final class Mutex: Locking { | |
private var mutex: pthread_mutex_t = { | |
var mutex = pthread_mutex_t() | |
pthread_mutex_init(&mutex, nil) | |
return mutex | |
}() | |
public func lock() { | |
pthread_mutex_lock(&mutex) | |
} | |
public func unlock() { | |
pthread_mutex_unlock(&mutex) | |
} | |
} | |
// MARK: - UnfairLock | |
/// A low-level lock that allows waiters to block efficiently on contention. | |
final class UnfairLock: Locking { | |
private var unfairLock = os_unfair_lock_s() | |
func lock() { | |
os_unfair_lock_lock(&unfairLock) | |
} | |
func unlock() { | |
os_unfair_lock_unlock(&unfairLock) | |
} | |
} | |
// MARK: - ReadersWriterLock | |
/// A readers-writer lock provided by the platform implementation of the POSIX Threads standard. | |
/// Read more: https://en.wikipedia.org/wiki/POSIX_Threads | |
public final class ReadersWriterLock { | |
private var rwlock: UnsafeMutablePointer<pthread_rwlock_t> | |
public init() { | |
rwlock = UnsafeMutablePointer.allocate(capacity: 1) | |
assert(pthread_rwlock_init(rwlock, nil) == 0) | |
} | |
deinit { | |
assert(pthread_rwlock_destroy(rwlock) == 0) | |
rwlock.deinitialize(count: 1) | |
rwlock.deallocate() | |
} | |
public func withReadLock<T>(body: () throws -> T) rethrows -> T { | |
pthread_rwlock_rdlock(rwlock) | |
defer { | |
pthread_rwlock_unlock(rwlock) | |
} | |
return try body() | |
} | |
public func withWriteLock<T>(body: () throws -> T) rethrows -> T { | |
pthread_rwlock_wrlock(rwlock) | |
defer { | |
pthread_rwlock_unlock(rwlock) | |
} | |
return try body() | |
} | |
} | |
// MARK: - Atomic | |
@propertyWrapper | |
public final class Atomic<L: Locking, T> { | |
private let lock: L | |
private var value: T | |
public init(wrappedValue: T, _ lock: L.Type) { | |
self.lock = L.init() | |
self.value = wrappedValue | |
} | |
public var wrappedValue: T { | |
get { | |
var value: T! = nil | |
lock.lock() | |
value = self.value | |
lock.unlock() | |
return value | |
} | |
set { | |
lock.lock() | |
self.value = newValue | |
lock.unlock() | |
} | |
} | |
/// Used for multi-statement atomic access to the wrapped property. | |
/// This is especially useful to wrap index-subscripts in value type collections that | |
/// otherwise would result in a call to get, a value copy and a subsequent call to set. | |
public func mutate(_ block: (inout T) -> Void) { | |
lock.lock() | |
block(&value) | |
lock.unlock() | |
} | |
/// The $-prefixed value. | |
public var projectedValue: Atomic<L, T> { self } | |
} | |
// MARK: - SyncDispatchQueueAtomic | |
@propertyWrapper | |
public final class SyncDispatchQueueAtomic<T> { | |
private let queue = DispatchQueue(label: "SyncDispatchQueueAtomic.\(UUID().uuidString)") | |
private var value: T | |
public init(wrappedValue: T) { | |
self.value = wrappedValue | |
} | |
public var wrappedValue: T { | |
get { queue.sync { value } } | |
set { queue.sync { value = newValue } } | |
} | |
/// Used for multi-statement atomic access to the wrapped property. | |
/// This is especially useful to wrap index-subscripts in value type collections that | |
/// otherwise would result in a call to get, a value copy and a subsequent call to set. | |
public func mutate(_ block: (inout T) -> Void) { | |
queue.sync { | |
block(&value) | |
} | |
} | |
/// The $-prefixed value. | |
public var projectedValue: SyncDispatchQueueAtomic<T> { self } | |
} | |
// MARK: - ReadersWriterAtomic | |
@propertyWrapper | |
public final class ReadersWriterAtomic<T> { | |
private let lock = ReadersWriterLock() | |
private var value: T | |
public init(wrappedValue: T) { | |
self.value = wrappedValue | |
} | |
public var wrappedValue: T { | |
get { | |
var value: T! = nil | |
lock.withReadLock { | |
value = self.value | |
} | |
return value | |
} | |
set { | |
lock.withWriteLock { | |
self.value = newValue | |
} | |
} | |
} | |
/// Used for multi-statement atomic access to the wrapped property. | |
/// This is especially useful to wrap index-subscripts in value type collections that | |
/// otherwise would result in a call to get, a value copy and a subsequent call to set. | |
public func mutate(_ block: (inout T) -> Void) { | |
lock.withWriteLock { | |
block(&value) | |
} | |
} | |
/// The $-prefixed value. | |
public var projectedValue: ReadersWriterAtomic<T> { self } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment