2
votes

In the actor example from the official kotlinlang.org documentation, an actor is launched 100 000 times which simply increments a counter inside the actor. Then a get request is sent to the actor and the counter is sent in the response with the correct amount (100 000).

This is the code:

// The messages
sealed class CounterMsg
object IncCounter : CounterMsg() // one-way message to increment counter
class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a two-way message to get the counter

// The actor
fun CoroutineScope.counterActor() = actor<CounterMsg> { 
    var counter = 0 // actor state
    for (msg in channel) { // iterate over incoming messages
        when (msg) {
            is IncCounter -> counter++
            is GetCounter -> msg.response.complete(counter)
        }
    }
}

fun main() {
    runBlocking {
        val counterActor = counterActor()
        GlobalScope.massiveRun {
            counterActor.send(IncCounter) // run action 100000 times
        }
        val response = CompletableDeferred<Int>()
        counterActor.send(GetCounter(response))
        println("Counter = ${response.await()}")
        counterActor.close()
    }
}

I have problems understanding what would happen if the counterActor coroutines would execute on multiple threads? If the coroutines would run on different threads the variable 'counter' in the actor would potentially be susceptible to a race condition, would it not?

Example: One thread runs a coroutine and this receives on the channel, and then on another thread a coroutine could receive and both of them try to update the counter variable at the same time, thus updating the variable incorrectly.

In the text that follows the code example

It does not matter (for correctness) what context the actor itself is executed in. An actor is a coroutine and a coroutine is executed sequentially, so confinement of the state to the specific coroutine works as a solution to the problem of shared mutable state.

Im having a hard time understanding this. Could someone elaborate what this exactly means, and why a race condition does nor occur. When I run the example I see all coroutines run on the same main thread so I can not prove my theory of the race condition.

1

1 Answers

3
votes

"actor is launched 100 000 times"

No, actor is launched exactly 1 time, at the line

val counterActor = counterActor()

Then it receives 100000 messages, from 100 coroutines working in parallel on different threads. But they do not increment the variable counter directly, they only add messages to the actor's input message queue. Indeed, this operation, implemented in the kotlinx.coroutines library, is made thread-safe.