3

I have a global variable that is accessed from multiple threads, including from the main thread. I’d like to use NSLock because it’s faster than GCD.

Here’s what I’m trying to do:

struct SynchronizedLock<Value> {
    private var _value: Value
    private var lock = NSLock()

    init(_ value: Value) {
        self._value = value
    }

    var value: Value {
        get { lock.synchronized { _value } }
        set { lock.synchronized { _value = newValue } }
    }

    mutating func synchronized<T>(block: (inout Value) throws -> T) rethrows -> T {
        return try lock.synchronized {
            try block(&_value)
        }
    }
}

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

Would NSLock block the main thread or is it safe to use on the main thread? Also is this the same situation with DispatchSemaphore and should resort to queues?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
TruMan1
  • 33,665
  • 59
  • 184
  • 335
  • It depends on how your threads are using it. If it's recursive then you probably need to look for NSRecursiveLock. If not then you just need to make sure lock/unlock call gets from the same thread. – VVB Oct 04 '19 at 00:18

1 Answers1

5

Yes, it’s safe to use NSLock from any thread, including the main thread. The only constraint with NSLock is that you must unlock it from the same thread that you locked it, which you are doing here.

Would NSLock block the main thread or is it safe to use on the main thread?

Obviously, if you block the main thread for any extensive period of time, it would be problematic. So make sure you always get in and out very quickly. Always avoid locking (or blocking) for any prolonged period of time.

Also is this the same situation with DispatchSemaphore and should resort to queues?

Any synchronization mechanism can block the thread from which they’re being used, so it’s largely problematic regardless of the synchronization mechanism. DispatchSemaphore or GCD serial queues both will have the same issue as this locking pattern.

You can always use the reader-writer pattern, which mitigates this slightly (where it allows concurrent reads and only blocks writes).

But as a general rule, constrain how much you do within a synchronization mechanism. E.g. if you’re doing something expensive, do as much as you can within locals to the particular thread, and only synchronize the final update of the shared resource.

Rob
  • 415,655
  • 72
  • 787
  • 1,044