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?
Yes, it’s safe to use
NSLockfrom any thread, including the main thread. The only constraint withNSLockis that you must unlock it from the same thread that you locked it, which you are doing here.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.
Any synchronization mechanism can block the thread from which they’re being used, so it’s largely problematic regardless of the synchronization mechanism.
DispatchSemaphoreor 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.