Hello guys,
I'm implementing a COW value type, and I'm confused whether I should use lock facilities to achieve thread safety.
For instance, here is my COW tyoe.
class Storage {
var value: Int
// and many other stuff
init(_ value: Int) { self.value = value }
func copy() -> Storage { return Storage(value) }
}
struct Value {
private var storage: Storage
init(_ value: Int) {
storage = Storage(value)
}
func get() -> Int { return storage.Int }
mutable func update(_ value: Int) {
if !isKnownUniquelyReferenced(&storage) { // (a)
storage = storage.copy() // (b)
}
storage.value = value // (c)
}
}
After some digging in this forum, I understand isKnownUniquelyReferenced
is thread safe as long as the caller does not create race condition on a same Value
variable.
But are the codes above enough to guarantee thread safety? Are there any mistakes if I use Value
in the following way?
var v1 = Value(3)
// v1 will always be mutated on queue1
let queue1 = DispatchQueue("queue1")
let queue2 = DispatchQueue("queue2")
queue1.main.async {
v1.update(5)
}
queue2.main.async { [var v2 = v1] in
v2.update(3)
}
It seems perfectly fine for me, because semantically Value
is a value type, and when queue2 makes a copy with v2 = v1
, everything should be OK just like Array
.
However, after closer analyses, I'm not so sure about that. Here's what I think:
There're 2 threads, let's call them T1, T2. During runtime, the order of executions of lines (a), (b), (c) in the 2 threads could be:
T1: (a) ---> (c)
T2: (a) ---> (b)
But when combined, I believe it is totally possible that the order of executions are as follows, because the copy v2 = v1
happens after v1.update(5)
is submitted on queue1.
T1 (a)
v2 is created, reference on storage +1
T2 (a)
T1 (c) (sorry I mistyped this line before)
T2 (b)
Now there's a race condition on the shared object Storage
, the object is mutated and fetched (for copy) at the same time: the object is used for copy during mutating - god knows what the values will be in the copy.
So which part of my codes is flawed?
Do I need to wrap my COW implementation in locks?
If I do, witch part of my codes should I change, the methods of Storage
or Value
?
Or is the caller to blame for the race condition happening here?