carlhung
(Carlhung)
1
as title.
Do I need to lock when reading a value?
for example:
@propertyWrapper
struct Protected<T> {
private let locker: NSLock = NSLock()
private var storedValue: T
init(wrappedValue: T) {
self.storedValue = wrappedValue
}
var wrappedValue: T {
get {
locker.lock()
defer {
locker.unlock()
}
return storedValue
}
set {
locker.lock()
defer {
locker.unlock()
}
storedValue = newValue
}
}
}
do I need to use lock() in the getter?
Using of NSLock assumes that you have a mutable state protected by this lock. In general the answer is yes – lock() should be called before reading to prevent reading when a writing is done by another thread at the same time.
1 Like
This sample is technically correct, but be careful. get and set are protected separately and there is no guarantee they prevent data races even they protect from memory corruption.
@Protected var counter: Int = 0
for _ in 1...1000 {
DispatchQueue.async { counter += 1 }
}
In the example above every time counter += 1 is executed, the following is done:
- a copy of
counter is made
- this copy is mutated
- this copy is then set back to
counter property
In practice, at some moment the following happens:
- 4 (or any other number) threads copy the same
counter value. e.g. 0
- all of these threads increment 0 to 1
- all of these threads write 1 back to
counter property
Finally, counter property is equal to 1 while you expect 4
In some cases this solution is acceptable, in others it is not.
3 Likes
carlhung
(Carlhung)
4
what is the best way? I also recognise this. I think it is better use class instead of struct.
vns
5
I would also suggest to use withLock instead of lock/unlock pair in most of the cases. Much less options to make a mistake.
Yes, class is much more robust in that case.
1 Like
tera
6
Great answers above, and if you are to do "counter += 1" you could perform the whole operation under lock, e.g.:
counter.locker.withLock {
counter.storedValue += 1
}
which you could wrap a bit further in a nicer API.
As an alternative consider atomics (if T is a simple type like Int).
BTW, class instead of struct does feel safer and more correct in this case, otherwise you'd need to worry what would happen if you copy a variable and perform operations on both copies (the lock will be shared, the values will be different, the overall semantics will be non-obvious).
1 Like
Like this e.g.:
public final class Protected<A> {
public var value: A {
lock.withLock { _value }
}
public func mutate(_ transform: (inout A) -> Void) {
lock.withLock { transform(&self._value) }
}
}
1 Like