Make a shared counter safe when a thousand coroutines increment it concurrently
A thousand coroutines on Dispatchers.Default each increment one shared counter. The final value is almost never 1000.
Constraints: keep the coroutines concurrent (do not confine them all to one thread), do not block a pool thread while waiting, and the result must be exactly 1000.
class Counter {
private var count = 0
suspend fun incrementAll(): Int = coroutineScope {
repeat(1000) {
launch(Dispatchers.Default) {
// your code here
}
}
count
}
}
Write the implementation.
count++ is a read-modify-write, so concurrent coroutines overwrite each other's updates. Guard it with mutex.withLock { count++ } — acquiring suspends rather than blocking a pool thread. An AtomicInteger is the cheaper lock-free alternative.
- ✗Thinking
@Volatilemakescount++an atomic operation - ✗Assuming an
Intincrement is atomic on the JVM - ✗Holding a blocking
synchronizedlock across a suspension point
- →When would an
AtomicIntegerbe preferable to aMutexhere? - →How does
limitedParallelism(1)solve the same race without any lock?
Why the increments are lost
count++ compiles into three steps: read, add, write. A thousand coroutines on the Dispatchers.Default pool interleave them, and two threads that read the same value write the same value back — one increment is gone.
The Mutex solution
class Counter {
private val mutex = Mutex()
private var count = 0
suspend fun incrementAll(): Int = coroutineScope {
repeat(1000) {
launch(Dispatchers.Default) {
mutex.withLock { count++ }
}
}
count
}
}
withLock suspends the coroutine while the lock is taken — the pool thread is released and picks up other work. coroutineScope waits for every child, so count is read only after they finish.
The lock-free alternative
class Counter {
private val count = AtomicInteger(0)
suspend fun incrementAll(): Int = coroutineScope {
repeat(1000) {
launch(Dispatchers.Default) { count.incrementAndGet() }
}
count.get()
}
}
For a single counter this is cheaper: incrementAndGet() is one atomic instruction and there is no waiting at all. A Mutex wins once several related fields must change together.