3

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!

Renuka Pandey
  • 1,494
  • 2
  • 13
  • 27

2 Answers2

9

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 conforms to NSLocking, you can use extensions designed for that. E.g., here is a common method that we use to guarantee that our locks and unlocks are balanced:

extension NSLocking {
    func synchronized<T>(block: () throws -> T) rethrows -> T {
        lock()
        defer { unlock() }
        return try block()
    }
}

And

lock.synchronized {
    // critical section here
}

But, bottom line, do not 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.

Rob
  • 392,368
  • 70
  • 743
  • 952
-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)
Kamran
  • 14,386
  • 3
  • 30
  • 47
  • Thank you very much, I was confused with "OS_UNFAIR_LOCK_INIT" – Renuka Pandey Jan 29 '20 at 09:04
  • 2
    Do not use `os_unfair_lock_s` from Swift like this. – Rob Aug 02 '21 at 01:36
  • @Rob: If `var unfairLock` is a file scope variable then it has a stable memory address, correct? – Martin R Aug 02 '21 at 14:10
  • @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… – Rob Aug 02 '21 at 16:30
  • 1
    @Rob: It is mentioned here https://forums.swift.org/t/exposing-the-memory-locations-of-class-instance-variables/30584 and here https://forums.swift.org/t/kvo-context-param/4203/2 that top-level and static variables have a stable address. – Martin R Aug 02 '21 at 20:03
  • @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… – Rob Aug 03 '21 at 01:29