Skip to content

Instantly share code, notes, and snippets.

@alexdrone
Created May 4, 2021 10:26
Show Gist options
  • Save alexdrone/247eac5ac5ea25fd0a8fa5cd2da06457 to your computer and use it in GitHub Desktop.
Save alexdrone/247eac5ac5ea25fd0a8fa5cd2da06457 to your computer and use it in GitHub Desktop.
Locks and Atomic Property wrappers.
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