'OSSpinLock' was deprecated in iOS 10.0: Use os_unfair_lock() from <os/lock.h> instead
Asked Answered
C

2

5

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.

enter image description here

Thanks!

Clem answered 29/1, 2020 at 8:3 Comment(0)
T
-3

You can use os_unfair_lock as below,

var unfairLock = os_unfair_lock_s()

os_unfair_lock_lock(&unfairLock)
os_unfair_lock_unlock(&unfairLock)
Tailing answered 29/1, 2020 at 8:31 Comment(7)
Thank you very much, I was confused with "OS_UNFAIR_LOCK_INIT"Clem
Do not use os_unfair_lock_s from Swift like this.Bal
@Rob: If var unfairLock is a file scope variable then it has a stable memory address, correct?Roee
@MartinR - I have seen no formal assurances to that end. And even if it was, it seems like an implementation detail upon which I wouldn’t be inclined to depend. I’d be fascinated to better understand this. I’m going entirely upon that parenthetical reference in that video…Bal
@Rob: It is mentioned here forums.swift.org/t/… and here forums.swift.org/t/kvo-context-param/4203/2 that top-level and static variables have a stable address.Roee
@MartinR - Thank you for the links! It looks like they’re contemplating alternatives (though I infer that allocate/initialize is still the status quo). The idea of global/statics for locks doesn’t seem practical…Bal
Needless to say, OSAllocatedUnfairLock is clearly the new status quo. Lol.Bal
B
17

In iOS 16 (and macOS 13) and later, you should use OSAllocatedUnfairLock. As the documentation says:

it’s unsafe to use os_unfair_lock from Swift because it’s a value type and, therefore, doesn’t have a stable memory address. That means when you call os_unfair_lock_lock or os_unfair_lock_unlock and 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 an OSAllocatedUnfairLock control 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.

Bal answered 8/3, 2021 at 7:17 Comment(0)
T
-3

You can use os_unfair_lock as below,

var unfairLock = os_unfair_lock_s()

os_unfair_lock_lock(&unfairLock)
os_unfair_lock_unlock(&unfairLock)
Tailing answered 29/1, 2020 at 8:31 Comment(7)
Thank you very much, I was confused with "OS_UNFAIR_LOCK_INIT"Clem
Do not use os_unfair_lock_s from Swift like this.Bal
@Rob: If var unfairLock is a file scope variable then it has a stable memory address, correct?Roee
@MartinR - I have seen no formal assurances to that end. And even if it was, it seems like an implementation detail upon which I wouldn’t be inclined to depend. I’d be fascinated to better understand this. I’m going entirely upon that parenthetical reference in that video…Bal
@Rob: It is mentioned here forums.swift.org/t/… and here forums.swift.org/t/kvo-context-param/4203/2 that top-level and static variables have a stable address.Roee
@MartinR - Thank you for the links! It looks like they’re contemplating alternatives (though I infer that allocate/initialize is still the status quo). The idea of global/statics for locks doesn’t seem practical…Bal
Needless to say, OSAllocatedUnfairLock is clearly the new status quo. Lol.Bal

© 2022 - 2025 — McMap. All rights reserved.