blob: 30d7334e6ce9f7aeb29d08796da9b066930f03a6 [file] [log] [blame] [view]
hadihariri7db55532018-09-15 10:35:08 +02001<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
2/*
Roman Elizarovdb0ef0c2019-07-03 15:02:44 +03003 * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
hadihariri7db55532018-09-15 10:35:08 +02004 */
5
6// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
Roman Elizarov0950dfa2018-07-13 10:33:25 +03007package kotlinx.coroutines.guide.$$1$$2
hadihariri7db55532018-09-15 10:35:08 +02008-->
Vsevolod Tolstopyatove50a0fa2019-01-28 11:34:24 +03009<!--- KNIT ../kotlinx-coroutines-core/jvm/test/guide/.*\.kt -->
10<!--- TEST_OUT ../kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt
hadihariri7db55532018-09-15 10:35:08 +020011// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
Roman Elizarov0950dfa2018-07-13 10:33:25 +030012package kotlinx.coroutines.guide.test
hadihariri7db55532018-09-15 10:35:08 +020013
14import org.junit.Test
15
16class SharedStateGuideTest {
17-->
Prendotab8a559d2018-11-30 16:24:23 +030018**Table of contents**
hadihariri7db55532018-09-15 10:35:08 +020019
20<!--- TOC -->
21
22* [Shared mutable state and concurrency](#shared-mutable-state-and-concurrency)
23 * [The problem](#the-problem)
24 * [Volatiles are of no help](#volatiles-are-of-no-help)
25 * [Thread-safe data structures](#thread-safe-data-structures)
26 * [Thread confinement fine-grained](#thread-confinement-fine-grained)
27 * [Thread confinement coarse-grained](#thread-confinement-coarse-grained)
28 * [Mutual exclusion](#mutual-exclusion)
29 * [Actors](#actors)
30
31<!--- END_TOC -->
32
33## Shared mutable state and concurrency
34
35Coroutines can be executed concurrently using a multi-threaded dispatcher like the [Dispatchers.Default]. It presents
36all the usual concurrency problems. The main problem being synchronization of access to **shared mutable state**.
37Some solutions to this problem in the land of coroutines are similar to the solutions in the multi-threaded world,
38but others are unique.
39
40### The problem
41
42Let us launch a hundred coroutines all doing the same action thousand times.
43We'll also measure their completion time for further comparisons:
44
Robert Golusińskic33ef612018-10-23 11:29:56 +020045<div class="sample" markdown="1" theme="idea" data-highlight-only>
Alexander Prendotacbeef102018-09-27 18:42:04 +030046
hadihariri7db55532018-09-15 10:35:08 +020047```kotlin
Roman Elizarovd94652f2019-06-01 14:18:42 +030048suspend fun massiveRun(action: suspend () -> Unit) {
hadihariri7db55532018-09-15 10:35:08 +020049 val n = 100 // number of coroutines to launch
50 val k = 1000 // times an action is repeated by each coroutine
51 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +030052 coroutineScope { // scope for coroutines
53 repeat(n) {
54 launch {
55 repeat(k) { action() }
56 }
hadihariri7db55532018-09-15 10:35:08 +020057 }
58 }
hadihariri7db55532018-09-15 10:35:08 +020059 }
60 println("Completed ${n * k} actions in $time ms")
61}
62```
63
Alexander Prendotacbeef102018-09-27 18:42:04 +030064</div>
65
hadihariri7db55532018-09-15 10:35:08 +020066We start with a very simple action that increments a shared mutable variable using
Roman Elizarovd94652f2019-06-01 14:18:42 +030067multi-threaded [Dispatchers.Default].
hadihariri7db55532018-09-15 10:35:08 +020068
Prendota0eee3c32018-10-22 12:52:56 +030069<!--- CLEAR -->
70
71<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +030072
hadihariri7db55532018-09-15 10:35:08 +020073```kotlin
Prendota0eee3c32018-10-22 12:52:56 +030074import kotlinx.coroutines.*
75import kotlin.system.*
76
Roman Elizarovd94652f2019-06-01 14:18:42 +030077suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +030078 val n = 100 // number of coroutines to launch
79 val k = 1000 // times an action is repeated by each coroutine
80 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +030081 coroutineScope { // scope for coroutines
82 repeat(n) {
83 launch {
84 repeat(k) { action() }
85 }
Prendota0eee3c32018-10-22 12:52:56 +030086 }
87 }
Prendota0eee3c32018-10-22 12:52:56 +030088 }
89 println("Completed ${n * k} actions in $time ms")
90}
91
Roman Elizarovd94652f2019-06-01 14:18:42 +030092//sampleStart
hadihariri7db55532018-09-15 10:35:08 +020093var counter = 0
94
Roman Elizarovd94652f2019-06-01 14:18:42 +030095fun main() = runBlocking {
96 withContext(Dispatchers.Default) {
97 massiveRun {
98 counter++
99 }
hadihariri7db55532018-09-15 10:35:08 +0200100 }
101 println("Counter = $counter")
102}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300103//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200104```
105
Alexander Prendotacbeef102018-09-27 18:42:04 +0300106</div>
107
Inego69c26df2019-04-21 14:51:25 +0700108> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt).
hadihariri7db55532018-09-15 10:35:08 +0200109
110<!--- TEST LINES_START
111Completed 100000 actions in
112Counter =
113-->
114
renzhi(任智)86559c62019-05-20 17:10:26 +0800115What does it print at the end? It is highly unlikely to ever print "Counter = 100000", because a hundred coroutines
hadihariri7db55532018-09-15 10:35:08 +0200116increment the `counter` concurrently from multiple threads without any synchronization.
117
hadihariri7db55532018-09-15 10:35:08 +0200118### Volatiles are of no help
119
120There is common misconception that making a variable `volatile` solves concurrency problem. Let us try it:
121
Prendota0eee3c32018-10-22 12:52:56 +0300122<!--- CLEAR -->
123
124<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300125
hadihariri7db55532018-09-15 10:35:08 +0200126```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300127import kotlinx.coroutines.*
128import kotlin.system.*
129
Roman Elizarovd94652f2019-06-01 14:18:42 +0300130suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300131 val n = 100 // number of coroutines to launch
132 val k = 1000 // times an action is repeated by each coroutine
133 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300134 coroutineScope { // scope for coroutines
135 repeat(n) {
136 launch {
137 repeat(k) { action() }
138 }
Prendota0eee3c32018-10-22 12:52:56 +0300139 }
140 }
Prendota0eee3c32018-10-22 12:52:56 +0300141 }
142 println("Completed ${n * k} actions in $time ms")
143}
144
Roman Elizarovd94652f2019-06-01 14:18:42 +0300145//sampleStart
hadihariri7db55532018-09-15 10:35:08 +0200146@Volatile // in Kotlin `volatile` is an annotation
147var counter = 0
148
Roman Elizarovd94652f2019-06-01 14:18:42 +0300149fun main() = runBlocking {
150 withContext(Dispatchers.Default) {
151 massiveRun {
152 counter++
153 }
hadihariri7db55532018-09-15 10:35:08 +0200154 }
155 println("Counter = $counter")
156}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300157//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200158```
159
Alexander Prendotacbeef102018-09-27 18:42:04 +0300160</div>
161
Inego69c26df2019-04-21 14:51:25 +0700162> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt).
hadihariri7db55532018-09-15 10:35:08 +0200163
164<!--- TEST LINES_START
165Completed 100000 actions in
166Counter =
167-->
168
169This code works slower, but we still don't get "Counter = 100000" at the end, because volatile variables guarantee
170linearizable (this is a technical term for "atomic") reads and writes to the corresponding variable, but
171do not provide atomicity of larger actions (increment in our case).
172
173### Thread-safe data structures
174
175The general solution that works both for threads and for coroutines is to use a thread-safe (aka synchronized,
176linearizable, or atomic) data structure that provides all the necessarily synchronization for the corresponding
177operations that needs to be performed on a shared state.
178In the case of a simple counter we can use `AtomicInteger` class which has atomic `incrementAndGet` operations:
179
Prendota0eee3c32018-10-22 12:52:56 +0300180<!--- CLEAR -->
181
182<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300183
hadihariri7db55532018-09-15 10:35:08 +0200184```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300185import kotlinx.coroutines.*
186import java.util.concurrent.atomic.*
187import kotlin.system.*
188
Roman Elizarovd94652f2019-06-01 14:18:42 +0300189suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300190 val n = 100 // number of coroutines to launch
191 val k = 1000 // times an action is repeated by each coroutine
192 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300193 coroutineScope { // scope for coroutines
194 repeat(n) {
195 launch {
196 repeat(k) { action() }
197 }
Prendota0eee3c32018-10-22 12:52:56 +0300198 }
199 }
Prendota0eee3c32018-10-22 12:52:56 +0300200 }
201 println("Completed ${n * k} actions in $time ms")
202}
203
Roman Elizarovd94652f2019-06-01 14:18:42 +0300204//sampleStart
hadihariri7db55532018-09-15 10:35:08 +0200205var counter = AtomicInteger()
206
Roman Elizarovd94652f2019-06-01 14:18:42 +0300207fun main() = runBlocking {
208 withContext(Dispatchers.Default) {
209 massiveRun {
210 counter.incrementAndGet()
211 }
hadihariri7db55532018-09-15 10:35:08 +0200212 }
Roman Elizarovd94652f2019-06-01 14:18:42 +0300213 println("Counter = $counter")
hadihariri7db55532018-09-15 10:35:08 +0200214}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300215//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200216```
217
Alexander Prendotacbeef102018-09-27 18:42:04 +0300218</div>
219
Inego69c26df2019-04-21 14:51:25 +0700220> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt).
hadihariri7db55532018-09-15 10:35:08 +0200221
222<!--- TEST ARBITRARY_TIME
223Completed 100000 actions in xxx ms
224Counter = 100000
225-->
226
227This is the fastest solution for this particular problem. It works for plain counters, collections, queues and other
228standard data structures and basic operations on them. However, it does not easily scale to complex
229state or to complex operations that do not have ready-to-use thread-safe implementations.
230
231### Thread confinement fine-grained
232
233_Thread confinement_ is an approach to the problem of shared mutable state where all access to the particular shared
234state is confined to a single thread. It is typically used in UI applications, where all UI state is confined to
235the single event-dispatch/application thread. It is easy to apply with coroutines by using a
236single-threaded context.
237
Prendota0eee3c32018-10-22 12:52:56 +0300238<!--- CLEAR -->
239
240<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300241
hadihariri7db55532018-09-15 10:35:08 +0200242```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300243import kotlinx.coroutines.*
244import kotlin.system.*
245
Roman Elizarovd94652f2019-06-01 14:18:42 +0300246suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300247 val n = 100 // number of coroutines to launch
248 val k = 1000 // times an action is repeated by each coroutine
249 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300250 coroutineScope { // scope for coroutines
251 repeat(n) {
252 launch {
253 repeat(k) { action() }
254 }
Prendota0eee3c32018-10-22 12:52:56 +0300255 }
256 }
Prendota0eee3c32018-10-22 12:52:56 +0300257 }
258 println("Completed ${n * k} actions in $time ms")
259}
260
Roman Elizarovd94652f2019-06-01 14:18:42 +0300261//sampleStart
hadihariri7db55532018-09-15 10:35:08 +0200262val counterContext = newSingleThreadContext("CounterContext")
263var counter = 0
264
Roman Elizarovd94652f2019-06-01 14:18:42 +0300265fun main() = runBlocking {
266 withContext(Dispatchers.Default) {
267 massiveRun {
268 // confine each increment to a single-threaded context
269 withContext(counterContext) {
270 counter++
271 }
hadihariri7db55532018-09-15 10:35:08 +0200272 }
273 }
274 println("Counter = $counter")
275}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300276//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200277```
278
Alexander Prendotacbeef102018-09-27 18:42:04 +0300279</div>
280
Inego69c26df2019-04-21 14:51:25 +0700281> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt).
hadihariri7db55532018-09-15 10:35:08 +0200282
283<!--- TEST ARBITRARY_TIME
284Completed 100000 actions in xxx ms
285Counter = 100000
286-->
287
288This code works very slowly, because it does _fine-grained_ thread-confinement. Each individual increment switches
Roman Elizarovd94652f2019-06-01 14:18:42 +0300289from multi-threaded [Dispatchers.Default] context to the single-threaded context using
290[withContext(counterContext)][withContext] block.
hadihariri7db55532018-09-15 10:35:08 +0200291
292### Thread confinement coarse-grained
293
294In practice, thread confinement is performed in large chunks, e.g. big pieces of state-updating business logic
295are confined to the single thread. The following example does it like that, running each coroutine in
296the single-threaded context to start with.
hadihariri7db55532018-09-15 10:35:08 +0200297
Prendota0eee3c32018-10-22 12:52:56 +0300298<!--- CLEAR -->
299
300<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300301
hadihariri7db55532018-09-15 10:35:08 +0200302```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300303import kotlinx.coroutines.*
304import kotlin.system.*
305
Roman Elizarovd94652f2019-06-01 14:18:42 +0300306suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300307 val n = 100 // number of coroutines to launch
308 val k = 1000 // times an action is repeated by each coroutine
309 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300310 coroutineScope { // scope for coroutines
311 repeat(n) {
312 launch {
313 repeat(k) { action() }
314 }
Prendota0eee3c32018-10-22 12:52:56 +0300315 }
316 }
Prendota0eee3c32018-10-22 12:52:56 +0300317 }
318 println("Completed ${n * k} actions in $time ms")
319}
320
Roman Elizarovd94652f2019-06-01 14:18:42 +0300321//sampleStart
hadihariri7db55532018-09-15 10:35:08 +0200322val counterContext = newSingleThreadContext("CounterContext")
323var counter = 0
324
Roman Elizarovd94652f2019-06-01 14:18:42 +0300325fun main() = runBlocking {
326 // confine everything to a single-threaded context
327 withContext(counterContext) {
328 massiveRun {
329 counter++
330 }
hadihariri7db55532018-09-15 10:35:08 +0200331 }
332 println("Counter = $counter")
333}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300334//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200335```
336
Alexander Prendotacbeef102018-09-27 18:42:04 +0300337</div>
338
Inego69c26df2019-04-21 14:51:25 +0700339> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt).
hadihariri7db55532018-09-15 10:35:08 +0200340
341<!--- TEST ARBITRARY_TIME
342Completed 100000 actions in xxx ms
343Counter = 100000
344-->
345
346This now works much faster and produces correct result.
347
348### Mutual exclusion
349
350Mutual exclusion solution to the problem is to protect all modifications of the shared state with a _critical section_
351that is never executed concurrently. In a blocking world you'd typically use `synchronized` or `ReentrantLock` for that.
352Coroutine's alternative is called [Mutex]. It has [lock][Mutex.lock] and [unlock][Mutex.unlock] functions to
353delimit a critical section. The key difference is that `Mutex.lock()` is a suspending function. It does not block a thread.
354
355There is also [withLock] extension function that conveniently represents
356`mutex.lock(); try { ... } finally { mutex.unlock() }` pattern:
357
Prendota0eee3c32018-10-22 12:52:56 +0300358<!--- CLEAR -->
359
360<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300361
hadihariri7db55532018-09-15 10:35:08 +0200362```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300363import kotlinx.coroutines.*
364import kotlinx.coroutines.sync.*
365import kotlin.system.*
366
Roman Elizarovd94652f2019-06-01 14:18:42 +0300367suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300368 val n = 100 // number of coroutines to launch
369 val k = 1000 // times an action is repeated by each coroutine
370 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300371 coroutineScope { // scope for coroutines
372 repeat(n) {
373 launch {
374 repeat(k) { action() }
375 }
Prendota0eee3c32018-10-22 12:52:56 +0300376 }
377 }
Prendota0eee3c32018-10-22 12:52:56 +0300378 }
379 println("Completed ${n * k} actions in $time ms")
380}
381
Roman Elizarovd94652f2019-06-01 14:18:42 +0300382//sampleStart
hadihariri7db55532018-09-15 10:35:08 +0200383val mutex = Mutex()
384var counter = 0
385
Roman Elizarovd94652f2019-06-01 14:18:42 +0300386fun main() = runBlocking {
387 withContext(Dispatchers.Default) {
388 massiveRun {
389 // protect each increment with lock
390 mutex.withLock {
391 counter++
392 }
hadihariri7db55532018-09-15 10:35:08 +0200393 }
394 }
395 println("Counter = $counter")
396}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300397//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200398```
399
Alexander Prendotacbeef102018-09-27 18:42:04 +0300400</div>
401
Inego69c26df2019-04-21 14:51:25 +0700402> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt).
hadihariri7db55532018-09-15 10:35:08 +0200403
404<!--- TEST ARBITRARY_TIME
405Completed 100000 actions in xxx ms
406Counter = 100000
407-->
408
409The locking in this example is fine-grained, so it pays the price. However, it is a good choice for some situations
410where you absolutely must modify some shared state periodically, but there is no natural thread that this state
411is confined to.
412
413### Actors
414
Roman Elizarovd94652f2019-06-01 14:18:42 +0300415An [actor](https://en.wikipedia.org/wiki/Actor_model) is an entity made up of a combination of a coroutine,
416the state that is confined and encapsulated into this coroutine,
hadihariri7db55532018-09-15 10:35:08 +0200417and a channel to communicate with other coroutines. A simple actor can be written as a function,
418but an actor with a complex state is better suited for a class.
419
420There is an [actor] coroutine builder that conveniently combines actor's mailbox channel into its
421scope to receive messages from and combines the send channel into the resulting job object, so that a
422single reference to the actor can be carried around as its handle.
423
424The first step of using an actor is to define a class of messages that an actor is going to process.
425Kotlin's [sealed classes](https://kotlinlang.org/docs/reference/sealed-classes.html) are well suited for that purpose.
426We define `CounterMsg` sealed class with `IncCounter` message to increment a counter and `GetCounter` message
427to get its value. The later needs to send a response. A [CompletableDeferred] communication
428primitive, that represents a single value that will be known (communicated) in the future,
429is used here for that purpose.
430
Alexander Prendotacbeef102018-09-27 18:42:04 +0300431<div class="sample" markdown="1" theme="idea" data-highlight-only>
432
hadihariri7db55532018-09-15 10:35:08 +0200433```kotlin
434// Message types for counterActor
435sealed class CounterMsg
436object IncCounter : CounterMsg() // one-way message to increment counter
437class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
438```
439
Alexander Prendotacbeef102018-09-27 18:42:04 +0300440</div>
441
hadihariri7db55532018-09-15 10:35:08 +0200442Then we define a function that launches an actor using an [actor] coroutine builder:
443
Alexander Prendotacbeef102018-09-27 18:42:04 +0300444<div class="sample" markdown="1" theme="idea" data-highlight-only>
445
hadihariri7db55532018-09-15 10:35:08 +0200446```kotlin
447// This function launches a new counter actor
448fun CoroutineScope.counterActor() = actor<CounterMsg> {
449 var counter = 0 // actor state
450 for (msg in channel) { // iterate over incoming messages
451 when (msg) {
452 is IncCounter -> counter++
453 is GetCounter -> msg.response.complete(counter)
454 }
455 }
456}
457```
458
Alexander Prendotacbeef102018-09-27 18:42:04 +0300459</div>
460
hadihariri7db55532018-09-15 10:35:08 +0200461The main code is straightforward:
462
Prendota0eee3c32018-10-22 12:52:56 +0300463<!--- CLEAR -->
464
465<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300466
hadihariri7db55532018-09-15 10:35:08 +0200467```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300468import kotlinx.coroutines.*
469import kotlinx.coroutines.channels.*
470import kotlin.system.*
471
Roman Elizarovd94652f2019-06-01 14:18:42 +0300472suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300473 val n = 100 // number of coroutines to launch
474 val k = 1000 // times an action is repeated by each coroutine
475 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300476 coroutineScope { // scope for coroutines
477 repeat(n) {
478 launch {
479 repeat(k) { action() }
480 }
Prendota0eee3c32018-10-22 12:52:56 +0300481 }
482 }
Prendota0eee3c32018-10-22 12:52:56 +0300483 }
484 println("Completed ${n * k} actions in $time ms")
485}
486
487// Message types for counterActor
488sealed class CounterMsg
489object IncCounter : CounterMsg() // one-way message to increment counter
490class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
491
492// This function launches a new counter actor
493fun CoroutineScope.counterActor() = actor<CounterMsg> {
494 var counter = 0 // actor state
495 for (msg in channel) { // iterate over incoming messages
496 when (msg) {
497 is IncCounter -> counter++
498 is GetCounter -> msg.response.complete(counter)
499 }
500 }
501}
502
Prendota0eee3c32018-10-22 12:52:56 +0300503//sampleStart
Aleksandr Blokh6dc1bd52019-08-25 13:35:08 +0300504fun main() = runBlocking<Unit> {
hadihariri7db55532018-09-15 10:35:08 +0200505 val counter = counterActor() // create the actor
Roman Elizarovd94652f2019-06-01 14:18:42 +0300506 withContext(Dispatchers.Default) {
507 massiveRun {
508 counter.send(IncCounter)
509 }
hadihariri7db55532018-09-15 10:35:08 +0200510 }
511 // send a message to get a counter value from an actor
512 val response = CompletableDeferred<Int>()
513 counter.send(GetCounter(response))
514 println("Counter = ${response.await()}")
515 counter.close() // shutdown the actor
516}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300517//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200518```
519
Alexander Prendotacbeef102018-09-27 18:42:04 +0300520</div>
521
Inego69c26df2019-04-21 14:51:25 +0700522> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt).
hadihariri7db55532018-09-15 10:35:08 +0200523
524<!--- TEST ARBITRARY_TIME
525Completed 100000 actions in xxx ms
526Counter = 100000
527-->
528
529It does not matter (for correctness) what context the actor itself is executed in. An actor is
530a coroutine and a coroutine is executed sequentially, so confinement of the state to the specific coroutine
Roman Elizarovd94652f2019-06-01 14:18:42 +0300531works as a solution to the problem of shared mutable state. Indeed, actors may modify their own private state,
532but can only affect each other through messages (avoiding the need for any locks).
hadihariri7db55532018-09-15 10:35:08 +0200533
534Actor is more efficient than locking under load, because in this case it always has work to do and it does not
535have to switch to a different context at all.
536
Inegoebe519a2019-04-21 13:22:27 +0700537> Note that an [actor] coroutine builder is a dual of [produce] coroutine builder. An actor is associated
hadihariri7db55532018-09-15 10:35:08 +0200538 with the channel that it receives messages from, while a producer is associated with the channel that it
539 sends elements to.
Roman Elizarov99c28aa2018-09-23 18:42:36 +0300540
541<!--- MODULE kotlinx-coroutines-core -->
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300542<!--- INDEX kotlinx.coroutines -->
543[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300544[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300545[CompletableDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-completable-deferred/index.html
546<!--- INDEX kotlinx.coroutines.sync -->
547[Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
548[Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
549[Mutex.unlock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/unlock.html
550[withLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/with-lock.html
551<!--- INDEX kotlinx.coroutines.channels -->
552[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
553[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
Roman Elizarov99c28aa2018-09-23 18:42:36 +0300554<!--- END -->