I went through this Question but the provided solution didn't work. Can someone please explain any alternative approach or proper implementation using os_unfair_lock()?
when I am using 'OS_UNFAIR_LOCK_INIT', it seems unavailable.
Thanks!
I went through this Question but the provided solution didn't work. Can someone please explain any alternative approach or proper implementation using os_unfair_lock()?
when I am using 'OS_UNFAIR_LOCK_INIT', it seems unavailable.
Thanks!
In iOS 16 (and macOS 13) and later, you should use OSAllocatedUnfairLock. As the documentation says:
it’s unsafe to use
os_unfair_lockfrom Swift because it’s a value type and, therefore, doesn’t have a stable memory address. That means when you callos_unfair_lock_lockoros_unfair_lock_unlockand pass a lock object using the&operator, the system may lock or unlock the wrong object.Instead, use
OSAllocatedUnfairLock, which avoids that pitfall because it doesn’t function as a value type, despite being a structure. All copied instances of anOSAllocatedUnfairLockcontrol the same underlying lock allocation.
So, if you have a counter that you want to interact with in a thread-safe manner:
import os.lock
let counter = OSAllocatedUnfairLock(initialState: 0)
...
counter.withLock { value in
value += 1
}
...
counter.withLock { value in
print(value)
}
For support of earlier OS versions, see my original answer below.
In Concurrent Programming With GCD in Swift 3, they warn us that we cannot use os_unfair_lock directly in Swift because “Swift assumes that anything that is struct can be moved, and that doesn't work with a mutex or with a lock.”
In that video, the speaker suggests that if you must use os_unfair_lock, that you put this in an Objective-C class (which won't move the struct). Or if you look at some of the stdlib code, they show you can stay in Swift, but use a UnsafeMutablePointer instead of the struct directly. (Thanks to bscothern for confirming this pattern.)
So, for example, you can write an UnfairLock class that avoids this problem:
final class UnfairLock: NSLocking {
private let unfairLock: UnsafeMutablePointer<os_unfair_lock> = {
let pointer = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
pointer.initialize(to: os_unfair_lock())
return pointer
}()
deinit {
unfairLock.deinitialize(count: 1)
unfairLock.deallocate()
}
func lock() {
os_unfair_lock_lock(unfairLock)
}
func tryLock() -> Bool {
os_unfair_lock_trylock(unfairLock)
}
func unlock() {
os_unfair_lock_unlock(unfairLock)
}
}
Then you can do things like:
let lock = UnfairLock()
And then use lock and unlock like you would with NSLock, but using the more efficient os_unfair_lock behind the scenes:
lock.lock()
// critical section here
lock.unlock()
And because this UnfairLock conforms to NSLocking, you can use extensions designed for that. E.g., there is a very useful Foundation extension withLock for the NSLocking protocol. This is a useful way of ensuring that locks and unlocks are properly scoped/balanced, thereby avoiding the brittleness of the above lock/unlock pattern. You can do things like:
lock.withLock {
// critical section here
}
That will lock before the “critical section” runs, and unlock when complete, ensuring that everything is properly balanced, even if you have some “early exits” inside the critical section.
But, going back to the original question, never use os_unfair_lock from Swift without something like the above or as contemplated in that video, both of which provide a stable memory address for the lock.
You can use os_unfair_lock as below,
var unfairLock = os_unfair_lock_s()
os_unfair_lock_lock(&unfairLock)
os_unfair_lock_unlock(&unfairLock)