diff --git a/docs/topics/shared-mutable-state-and-concurrency.md b/docs/topics/shared-mutable-state-and-concurrency.md index 8e491b3d64..8b840a23d9 100644 --- a/docs/topics/shared-mutable-state-and-concurrency.md +++ b/docs/topics/shared-mutable-state-and-concurrency.md @@ -370,133 +370,11 @@ The locking in this example is fine-grained, so it pays the price. However, it i where you absolutely must modify some shared state periodically, but there is no natural thread that this state is confined to. -## Actors - -An [actor](https://en.wikipedia.org/wiki/Actor_model) is an entity made up of a combination of a coroutine, -the state that is confined and encapsulated into this coroutine, -and a channel to communicate with other coroutines. A simple actor can be written as a function, -but an actor with a complex state is better suited for a class. - -There is an [actor] coroutine builder that conveniently combines actor's mailbox channel into its -scope to receive messages from and combines the send channel into the resulting job object, so that a -single reference to the actor can be carried around as its handle. - -The first step of using an actor is to define a class of messages that an actor is going to process. -Kotlin's [sealed classes](https://kotlinlang.org/docs/reference/sealed-classes.html) are well suited for that purpose. -We define `CounterMsg` sealed class with `IncCounter` message to increment a counter and `GetCounter` message -to get its value. The latter needs to send a response. A [CompletableDeferred] communication -primitive, that represents a single value that will be known (communicated) in the future, -is used here for that purpose. - -```kotlin -// Message types for counterActor -sealed class CounterMsg -object IncCounter : CounterMsg() // one-way message to increment counter -class GetCounter(val response: CompletableDeferred) : CounterMsg() // a request with reply -``` - -Then we define a function that launches an actor using an [actor] coroutine builder: - -```kotlin -// This function launches a new counter actor -fun CoroutineScope.counterActor() = actor { - var counter = 0 // actor state - for (msg in channel) { // iterate over incoming messages - when (msg) { - is IncCounter -> counter++ - is GetCounter -> msg.response.complete(counter) - } - } -} -``` - -The main code is straightforward: - - - -```kotlin -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.* -import kotlin.system.* - -suspend fun massiveRun(action: suspend () -> Unit) { - val n = 100 // number of coroutines to launch - val k = 1000 // times an action is repeated by each coroutine - val time = measureTimeMillis { - coroutineScope { // scope for coroutines - repeat(n) { - launch { - repeat(k) { action() } - } - } - } - } - println("Completed ${n * k} actions in $time ms") -} - -// Message types for counterActor -sealed class CounterMsg -object IncCounter : CounterMsg() // one-way message to increment counter -class GetCounter(val response: CompletableDeferred) : CounterMsg() // a request with reply - -// This function launches a new counter actor -fun CoroutineScope.counterActor() = actor { - var counter = 0 // actor state - for (msg in channel) { // iterate over incoming messages - when (msg) { - is IncCounter -> counter++ - is GetCounter -> msg.response.complete(counter) - } - } -} - -//sampleStart -fun main() = runBlocking { - val counter = counterActor() // create the actor - withContext(Dispatchers.Default) { - massiveRun { - counter.send(IncCounter) - } - } - // send a message to get a counter value from an actor - val response = CompletableDeferred() - counter.send(GetCounter(response)) - println("Counter = ${response.await()}") - counter.close() // shutdown the actor -} -//sampleEnd -``` -{kotlin-runnable="true" kotlin-min-compiler-version="1.3"} - -> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt). -> -{type="note"} - - - -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. Indeed, actors may modify their own private state, -but can only affect each other through messages (avoiding the need for any locks). - -Actor is more efficient than locking under load, because in this case it always has work to do and it does not -have to switch to a different context at all. - -> Note that an [actor] coroutine builder is a dual of [produce] coroutine builder. An actor is associated -> with the channel that it receives messages from, while a producer is associated with the channel that it -> sends elements to. -> -{type="note"} - [Dispatchers.Default]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html [withContext]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html -[CompletableDeferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-completable-deferred/index.html @@ -505,9 +383,4 @@ have to switch to a different context at all. [Mutex.unlock]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/unlock.html [withLock]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/with-lock.html - - -[actor]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html -[produce]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html - diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt index 3162b24cbc..034b82efc9 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt @@ -56,12 +56,4 @@ class SharedStateGuideTest { "Counter = 100000" ) } - - @Test - fun testExampleSync07() { - test("ExampleSync07") { kotlinx.coroutines.guide.exampleSync07.main() }.verifyLinesArbitraryTime( - "Completed 100000 actions in xxx ms", - "Counter = 100000" - ) - } }