Let's say, there is a variable that I want to make thread safe. One of the most common ways to do this:
var value: A {
get { return queue.sync { self._value } }
set { queue.sync { self._value = newValue } }
}
However, this property is not completely thread safe if we change the value as in the example below:
Class.value += 1
So my question is: Using NSLock on the same principle is also not completely thread safe?
var value: A {
get {
lock.lock()
defer { lock.unlock() }
return self._value
}
set {
lock.lock()
defer { lock.unlock() }
self._value = newValue
}
}
That's interesting, I'm learning about this for the first time.
The issue in the first bit of code, is that:
object.value += 1
has the same semantics as
object.value = object.value + 1
which we can further expand to:
let originalValue = queue.sync { object._value }
let newValue = origiinalValue + 1
queue.sync { self._value = newValue }
Expanding it so makes it clear that the synchronization of the getter and setter work fine, but they're not synchronized as a whole. A context switch in the middle of the code above could cause _value
to be mutated by another thread, without newValue
reflecting the change.
Using a lock would have the exact same problem. It would expand to:
lock.lock()
let originalValue = object._value
lock.unlock()
let newValue = originalValue + 1
lock.lock()
object._value = newValue
lock.unlock()
You can see this for yourself by instrumenting your code with some logging statements, which show that the mutation isn't fully covered by the lock:
class C {
var lock = NSLock()
var _value: Int
var value: Int {
get {
print("value.get start")
print("lock.lock()")
lock.lock()
defer {
print("lock.unlock()")
lock.unlock()
print("value.get end")
}
print("getting self._value")
return self._value
}
set {
print("\n\n\nvalue.set start")
lock.lock()
print("lock.lock()")
defer {
print("lock.unlock()")
lock.unlock()
print("value.set end")
}
print("setting self._value")
self._value = newValue
}
}
init(_ value: Int) { self._value = value }
}
let object = C(0)
object.value += 1