swiftconcurrencygrand-central-dispatchnslock

Safe to use NSLock on main thread?


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?


Solution

  • 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.