I’ve inherited some code and I’m wondering about how thread-safe it is.
What’s bugging me is that the code makes an assignment to a var from within code that could be executed on any number of threads.
Here’s the idea:
data class Stuff(val number: Int)
data class Thing(val accumulation: Int)
suspend fun foo(): Int {
var count: Thing = Thing(0)
val listOfStuff = listOf<Stuff>(/* a bunch of Stuffs here */)
coroutineScope {
listOfStuff.map { s ->
async {
count = Thing(count.accumulation + s.number)
}
}
.awaitAll()
}
return count.accumulation
}
My question is, is the assignment to count thread safe?
Put another way: If those coroutines are run in parallel on different threads (and there’s nothing here preventing that happening) is there a possibility that the value of count will be wrong in the end?
Obviously, this could be fixed by making count an AtomicReference but I don’t want to go to that trouble if I don’t have to.
Ideally, if the answer is “yes, it is safe” please also provide a link to some docs or a spec I can read that will confirm that because I’m interested to learn how they do that. (It seems to me that it should NOT be safe.)
I don’t know coroutines, but if they run on multiple threads, then I strongly suspect that it’s NOT thread-safe. (I can’t quote you a reference, though.)
I think one failure mode is that when one thread updates count, another thread could see the updated value but NOT the initialised Thing instance it refers to. (There are probably additional failure modes too.)
Although that access could be made safe with an AtomicReference (or possibly a @Volatile annotation or some other technique), I think that would still leave a race condition where two threads could read the count.accumulation, and then both could create and assign Thing instances from it — one of their updates would be lost.
In this simple case, I think a much better approach would be for each coroutine to simply yield its value independently, and then the surrounding code to sum all the values. (Though, as I said, I don’t know coroutines so I can’t write the code.) Of course, your more complex real-world case may not allow that approach.