blob: 123b6dbdd5c73ef05f089902beb24b267986cd44 [file] [log] [blame] [view]
Roman Elizarov660c2d72020-02-14 13:18:37 +03001<!--- TEST_NAME SharedStateGuideTest -->
2
Prendotab8a559d2018-11-30 16:24:23 +03003**Table of contents**
hadihariri7db55532018-09-15 10:35:08 +02004
5<!--- TOC -->
6
7* [Shared mutable state and concurrency](#shared-mutable-state-and-concurrency)
8 * [The problem](#the-problem)
9 * [Volatiles are of no help](#volatiles-are-of-no-help)
10 * [Thread-safe data structures](#thread-safe-data-structures)
11 * [Thread confinement fine-grained](#thread-confinement-fine-grained)
12 * [Thread confinement coarse-grained](#thread-confinement-coarse-grained)
13 * [Mutual exclusion](#mutual-exclusion)
14 * [Actors](#actors)
15
Roman Elizarov660c2d72020-02-14 13:18:37 +030016<!--- END -->
hadihariri7db55532018-09-15 10:35:08 +020017
18## Shared mutable state and concurrency
19
20Coroutines can be executed concurrently using a multi-threaded dispatcher like the [Dispatchers.Default]. It presents
21all the usual concurrency problems. The main problem being synchronization of access to **shared mutable state**.
22Some solutions to this problem in the land of coroutines are similar to the solutions in the multi-threaded world,
23but others are unique.
24
25### The problem
26
Masood Fallahpoor3f108cb2020-10-16 10:54:35 +033027Let us launch a hundred coroutines all doing the same action a thousand times.
hadihariri7db55532018-09-15 10:35:08 +020028We'll also measure their completion time for further comparisons:
29
Robert Golusińskic33ef612018-10-23 11:29:56 +020030<div class="sample" markdown="1" theme="idea" data-highlight-only>
Alexander Prendotacbeef102018-09-27 18:42:04 +030031
hadihariri7db55532018-09-15 10:35:08 +020032```kotlin
Roman Elizarovd94652f2019-06-01 14:18:42 +030033suspend fun massiveRun(action: suspend () -> Unit) {
hadihariri7db55532018-09-15 10:35:08 +020034 val n = 100 // number of coroutines to launch
35 val k = 1000 // times an action is repeated by each coroutine
36 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +030037 coroutineScope { // scope for coroutines
38 repeat(n) {
39 launch {
40 repeat(k) { action() }
41 }
hadihariri7db55532018-09-15 10:35:08 +020042 }
43 }
hadihariri7db55532018-09-15 10:35:08 +020044 }
45 println("Completed ${n * k} actions in $time ms")
46}
47```
48
Alexander Prendotacbeef102018-09-27 18:42:04 +030049</div>
50
hadihariri7db55532018-09-15 10:35:08 +020051We start with a very simple action that increments a shared mutable variable using
Roman Elizarovd94652f2019-06-01 14:18:42 +030052multi-threaded [Dispatchers.Default].
hadihariri7db55532018-09-15 10:35:08 +020053
Prendota0eee3c32018-10-22 12:52:56 +030054<!--- CLEAR -->
55
56<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +030057
hadihariri7db55532018-09-15 10:35:08 +020058```kotlin
Prendota0eee3c32018-10-22 12:52:56 +030059import kotlinx.coroutines.*
60import kotlin.system.*
61
Roman Elizarovd94652f2019-06-01 14:18:42 +030062suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +030063 val n = 100 // number of coroutines to launch
64 val k = 1000 // times an action is repeated by each coroutine
65 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +030066 coroutineScope { // scope for coroutines
67 repeat(n) {
68 launch {
69 repeat(k) { action() }
70 }
Prendota0eee3c32018-10-22 12:52:56 +030071 }
72 }
Prendota0eee3c32018-10-22 12:52:56 +030073 }
74 println("Completed ${n * k} actions in $time ms")
75}
76
Roman Elizarovd94652f2019-06-01 14:18:42 +030077//sampleStart
hadihariri7db55532018-09-15 10:35:08 +020078var counter = 0
79
Roman Elizarovd94652f2019-06-01 14:18:42 +030080fun main() = runBlocking {
81 withContext(Dispatchers.Default) {
82 massiveRun {
83 counter++
84 }
hadihariri7db55532018-09-15 10:35:08 +020085 }
86 println("Counter = $counter")
87}
Roman Elizarovd94652f2019-06-01 14:18:42 +030088//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +020089```
90
Alexander Prendotacbeef102018-09-27 18:42:04 +030091</div>
92
Adam Howardf13549a2020-06-02 11:17:46 +010093> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt).
hadihariri7db55532018-09-15 10:35:08 +020094
95<!--- TEST LINES_START
96Completed 100000 actions in
97Counter =
98-->
99
renzhi(任智)86559c62019-05-20 17:10:26 +0800100What 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 +0200101increment the `counter` concurrently from multiple threads without any synchronization.
102
hadihariri7db55532018-09-15 10:35:08 +0200103### Volatiles are of no help
104
Masood Fallahpoor3f108cb2020-10-16 10:54:35 +0330105There is a common misconception that making a variable `volatile` solves concurrency problem. Let us try it:
hadihariri7db55532018-09-15 10:35:08 +0200106
Prendota0eee3c32018-10-22 12:52:56 +0300107<!--- CLEAR -->
108
109<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300110
hadihariri7db55532018-09-15 10:35:08 +0200111```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300112import kotlinx.coroutines.*
113import kotlin.system.*
114
Roman Elizarovd94652f2019-06-01 14:18:42 +0300115suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300116 val n = 100 // number of coroutines to launch
117 val k = 1000 // times an action is repeated by each coroutine
118 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300119 coroutineScope { // scope for coroutines
120 repeat(n) {
121 launch {
122 repeat(k) { action() }
123 }
Prendota0eee3c32018-10-22 12:52:56 +0300124 }
125 }
Prendota0eee3c32018-10-22 12:52:56 +0300126 }
127 println("Completed ${n * k} actions in $time ms")
128}
129
Roman Elizarovd94652f2019-06-01 14:18:42 +0300130//sampleStart
hadihariri7db55532018-09-15 10:35:08 +0200131@Volatile // in Kotlin `volatile` is an annotation
132var counter = 0
133
Roman Elizarovd94652f2019-06-01 14:18:42 +0300134fun main() = runBlocking {
135 withContext(Dispatchers.Default) {
136 massiveRun {
137 counter++
138 }
hadihariri7db55532018-09-15 10:35:08 +0200139 }
140 println("Counter = $counter")
141}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300142//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200143```
144
Alexander Prendotacbeef102018-09-27 18:42:04 +0300145</div>
146
Adam Howardf13549a2020-06-02 11:17:46 +0100147> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt).
hadihariri7db55532018-09-15 10:35:08 +0200148
149<!--- TEST LINES_START
150Completed 100000 actions in
151Counter =
152-->
153
154This code works slower, but we still don't get "Counter = 100000" at the end, because volatile variables guarantee
155linearizable (this is a technical term for "atomic") reads and writes to the corresponding variable, but
156do not provide atomicity of larger actions (increment in our case).
157
158### Thread-safe data structures
159
160The general solution that works both for threads and for coroutines is to use a thread-safe (aka synchronized,
Masood Fallahpoor3f108cb2020-10-16 10:54:35 +0330161linearizable, or atomic) data structure that provides all the necessary synchronization for the corresponding
hadihariri7db55532018-09-15 10:35:08 +0200162operations that needs to be performed on a shared state.
163In the case of a simple counter we can use `AtomicInteger` class which has atomic `incrementAndGet` operations:
164
Prendota0eee3c32018-10-22 12:52:56 +0300165<!--- CLEAR -->
166
167<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300168
hadihariri7db55532018-09-15 10:35:08 +0200169```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300170import kotlinx.coroutines.*
171import java.util.concurrent.atomic.*
172import kotlin.system.*
173
Roman Elizarovd94652f2019-06-01 14:18:42 +0300174suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300175 val n = 100 // number of coroutines to launch
176 val k = 1000 // times an action is repeated by each coroutine
177 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300178 coroutineScope { // scope for coroutines
179 repeat(n) {
180 launch {
181 repeat(k) { action() }
182 }
Prendota0eee3c32018-10-22 12:52:56 +0300183 }
184 }
Prendota0eee3c32018-10-22 12:52:56 +0300185 }
186 println("Completed ${n * k} actions in $time ms")
187}
188
Roman Elizarovd94652f2019-06-01 14:18:42 +0300189//sampleStart
Roman Elizarov1bd43492020-05-27 11:58:11 +0300190val counter = AtomicInteger()
hadihariri7db55532018-09-15 10:35:08 +0200191
Roman Elizarovd94652f2019-06-01 14:18:42 +0300192fun main() = runBlocking {
193 withContext(Dispatchers.Default) {
194 massiveRun {
195 counter.incrementAndGet()
196 }
hadihariri7db55532018-09-15 10:35:08 +0200197 }
Roman Elizarovd94652f2019-06-01 14:18:42 +0300198 println("Counter = $counter")
hadihariri7db55532018-09-15 10:35:08 +0200199}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300200//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200201```
202
Alexander Prendotacbeef102018-09-27 18:42:04 +0300203</div>
204
Adam Howardf13549a2020-06-02 11:17:46 +0100205> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt).
hadihariri7db55532018-09-15 10:35:08 +0200206
207<!--- TEST ARBITRARY_TIME
208Completed 100000 actions in xxx ms
209Counter = 100000
210-->
211
212This is the fastest solution for this particular problem. It works for plain counters, collections, queues and other
213standard data structures and basic operations on them. However, it does not easily scale to complex
214state or to complex operations that do not have ready-to-use thread-safe implementations.
215
216### Thread confinement fine-grained
217
218_Thread confinement_ is an approach to the problem of shared mutable state where all access to the particular shared
219state is confined to a single thread. It is typically used in UI applications, where all UI state is confined to
220the single event-dispatch/application thread. It is easy to apply with coroutines by using a
221single-threaded context.
222
Prendota0eee3c32018-10-22 12:52:56 +0300223<!--- CLEAR -->
224
225<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300226
hadihariri7db55532018-09-15 10:35:08 +0200227```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300228import kotlinx.coroutines.*
229import kotlin.system.*
230
Roman Elizarovd94652f2019-06-01 14:18:42 +0300231suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300232 val n = 100 // number of coroutines to launch
233 val k = 1000 // times an action is repeated by each coroutine
234 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300235 coroutineScope { // scope for coroutines
236 repeat(n) {
237 launch {
238 repeat(k) { action() }
239 }
Prendota0eee3c32018-10-22 12:52:56 +0300240 }
241 }
Prendota0eee3c32018-10-22 12:52:56 +0300242 }
243 println("Completed ${n * k} actions in $time ms")
244}
245
Roman Elizarovd94652f2019-06-01 14:18:42 +0300246//sampleStart
hadihariri7db55532018-09-15 10:35:08 +0200247val counterContext = newSingleThreadContext("CounterContext")
248var counter = 0
249
Roman Elizarovd94652f2019-06-01 14:18:42 +0300250fun main() = runBlocking {
251 withContext(Dispatchers.Default) {
252 massiveRun {
253 // confine each increment to a single-threaded context
254 withContext(counterContext) {
255 counter++
256 }
hadihariri7db55532018-09-15 10:35:08 +0200257 }
258 }
259 println("Counter = $counter")
260}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300261//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200262```
263
Alexander Prendotacbeef102018-09-27 18:42:04 +0300264</div>
265
Adam Howardf13549a2020-06-02 11:17:46 +0100266> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt).
hadihariri7db55532018-09-15 10:35:08 +0200267
268<!--- TEST ARBITRARY_TIME
269Completed 100000 actions in xxx ms
270Counter = 100000
271-->
272
273This code works very slowly, because it does _fine-grained_ thread-confinement. Each individual increment switches
Roman Elizarovd94652f2019-06-01 14:18:42 +0300274from multi-threaded [Dispatchers.Default] context to the single-threaded context using
275[withContext(counterContext)][withContext] block.
hadihariri7db55532018-09-15 10:35:08 +0200276
277### Thread confinement coarse-grained
278
279In practice, thread confinement is performed in large chunks, e.g. big pieces of state-updating business logic
280are confined to the single thread. The following example does it like that, running each coroutine in
281the single-threaded context to start with.
hadihariri7db55532018-09-15 10:35:08 +0200282
Prendota0eee3c32018-10-22 12:52:56 +0300283<!--- CLEAR -->
284
285<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300286
hadihariri7db55532018-09-15 10:35:08 +0200287```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300288import kotlinx.coroutines.*
289import kotlin.system.*
290
Roman Elizarovd94652f2019-06-01 14:18:42 +0300291suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300292 val n = 100 // number of coroutines to launch
293 val k = 1000 // times an action is repeated by each coroutine
294 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300295 coroutineScope { // scope for coroutines
296 repeat(n) {
297 launch {
298 repeat(k) { action() }
299 }
Prendota0eee3c32018-10-22 12:52:56 +0300300 }
301 }
Prendota0eee3c32018-10-22 12:52:56 +0300302 }
303 println("Completed ${n * k} actions in $time ms")
304}
305
Roman Elizarovd94652f2019-06-01 14:18:42 +0300306//sampleStart
hadihariri7db55532018-09-15 10:35:08 +0200307val counterContext = newSingleThreadContext("CounterContext")
308var counter = 0
309
Roman Elizarovd94652f2019-06-01 14:18:42 +0300310fun main() = runBlocking {
311 // confine everything to a single-threaded context
312 withContext(counterContext) {
313 massiveRun {
314 counter++
315 }
hadihariri7db55532018-09-15 10:35:08 +0200316 }
317 println("Counter = $counter")
318}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300319//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200320```
321
Alexander Prendotacbeef102018-09-27 18:42:04 +0300322</div>
323
Adam Howardf13549a2020-06-02 11:17:46 +0100324> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt).
hadihariri7db55532018-09-15 10:35:08 +0200325
326<!--- TEST ARBITRARY_TIME
327Completed 100000 actions in xxx ms
328Counter = 100000
329-->
330
331This now works much faster and produces correct result.
332
333### Mutual exclusion
334
335Mutual exclusion solution to the problem is to protect all modifications of the shared state with a _critical section_
336that is never executed concurrently. In a blocking world you'd typically use `synchronized` or `ReentrantLock` for that.
337Coroutine's alternative is called [Mutex]. It has [lock][Mutex.lock] and [unlock][Mutex.unlock] functions to
338delimit a critical section. The key difference is that `Mutex.lock()` is a suspending function. It does not block a thread.
339
340There is also [withLock] extension function that conveniently represents
341`mutex.lock(); try { ... } finally { mutex.unlock() }` pattern:
342
Prendota0eee3c32018-10-22 12:52:56 +0300343<!--- CLEAR -->
344
345<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300346
hadihariri7db55532018-09-15 10:35:08 +0200347```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300348import kotlinx.coroutines.*
349import kotlinx.coroutines.sync.*
350import kotlin.system.*
351
Roman Elizarovd94652f2019-06-01 14:18:42 +0300352suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300353 val n = 100 // number of coroutines to launch
354 val k = 1000 // times an action is repeated by each coroutine
355 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300356 coroutineScope { // scope for coroutines
357 repeat(n) {
358 launch {
359 repeat(k) { action() }
360 }
Prendota0eee3c32018-10-22 12:52:56 +0300361 }
362 }
Prendota0eee3c32018-10-22 12:52:56 +0300363 }
364 println("Completed ${n * k} actions in $time ms")
365}
366
Roman Elizarovd94652f2019-06-01 14:18:42 +0300367//sampleStart
hadihariri7db55532018-09-15 10:35:08 +0200368val mutex = Mutex()
369var counter = 0
370
Roman Elizarovd94652f2019-06-01 14:18:42 +0300371fun main() = runBlocking {
372 withContext(Dispatchers.Default) {
373 massiveRun {
374 // protect each increment with lock
375 mutex.withLock {
376 counter++
377 }
hadihariri7db55532018-09-15 10:35:08 +0200378 }
379 }
380 println("Counter = $counter")
381}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300382//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200383```
384
Alexander Prendotacbeef102018-09-27 18:42:04 +0300385</div>
386
Adam Howardf13549a2020-06-02 11:17:46 +0100387> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt).
hadihariri7db55532018-09-15 10:35:08 +0200388
389<!--- TEST ARBITRARY_TIME
390Completed 100000 actions in xxx ms
391Counter = 100000
392-->
393
394The locking in this example is fine-grained, so it pays the price. However, it is a good choice for some situations
395where you absolutely must modify some shared state periodically, but there is no natural thread that this state
396is confined to.
397
398### Actors
399
Roman Elizarovd94652f2019-06-01 14:18:42 +0300400An [actor](https://en.wikipedia.org/wiki/Actor_model) is an entity made up of a combination of a coroutine,
401the state that is confined and encapsulated into this coroutine,
hadihariri7db55532018-09-15 10:35:08 +0200402and a channel to communicate with other coroutines. A simple actor can be written as a function,
403but an actor with a complex state is better suited for a class.
404
405There is an [actor] coroutine builder that conveniently combines actor's mailbox channel into its
406scope to receive messages from and combines the send channel into the resulting job object, so that a
407single reference to the actor can be carried around as its handle.
408
409The first step of using an actor is to define a class of messages that an actor is going to process.
410Kotlin's [sealed classes](https://kotlinlang.org/docs/reference/sealed-classes.html) are well suited for that purpose.
411We define `CounterMsg` sealed class with `IncCounter` message to increment a counter and `GetCounter` message
412to get its value. The later needs to send a response. A [CompletableDeferred] communication
413primitive, that represents a single value that will be known (communicated) in the future,
414is used here for that purpose.
415
Alexander Prendotacbeef102018-09-27 18:42:04 +0300416<div class="sample" markdown="1" theme="idea" data-highlight-only>
417
hadihariri7db55532018-09-15 10:35:08 +0200418```kotlin
419// Message types for counterActor
420sealed class CounterMsg
421object IncCounter : CounterMsg() // one-way message to increment counter
422class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
423```
424
Alexander Prendotacbeef102018-09-27 18:42:04 +0300425</div>
426
hadihariri7db55532018-09-15 10:35:08 +0200427Then we define a function that launches an actor using an [actor] coroutine builder:
428
Alexander Prendotacbeef102018-09-27 18:42:04 +0300429<div class="sample" markdown="1" theme="idea" data-highlight-only>
430
hadihariri7db55532018-09-15 10:35:08 +0200431```kotlin
432// This function launches a new counter actor
433fun CoroutineScope.counterActor() = actor<CounterMsg> {
434 var counter = 0 // actor state
435 for (msg in channel) { // iterate over incoming messages
436 when (msg) {
437 is IncCounter -> counter++
438 is GetCounter -> msg.response.complete(counter)
439 }
440 }
441}
442```
443
Alexander Prendotacbeef102018-09-27 18:42:04 +0300444</div>
445
hadihariri7db55532018-09-15 10:35:08 +0200446The main code is straightforward:
447
Prendota0eee3c32018-10-22 12:52:56 +0300448<!--- CLEAR -->
449
450<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
Alexander Prendotacbeef102018-09-27 18:42:04 +0300451
hadihariri7db55532018-09-15 10:35:08 +0200452```kotlin
Prendota0eee3c32018-10-22 12:52:56 +0300453import kotlinx.coroutines.*
454import kotlinx.coroutines.channels.*
455import kotlin.system.*
456
Roman Elizarovd94652f2019-06-01 14:18:42 +0300457suspend fun massiveRun(action: suspend () -> Unit) {
Prendota0eee3c32018-10-22 12:52:56 +0300458 val n = 100 // number of coroutines to launch
459 val k = 1000 // times an action is repeated by each coroutine
460 val time = measureTimeMillis {
Roman Elizarovd94652f2019-06-01 14:18:42 +0300461 coroutineScope { // scope for coroutines
462 repeat(n) {
463 launch {
464 repeat(k) { action() }
465 }
Prendota0eee3c32018-10-22 12:52:56 +0300466 }
467 }
Prendota0eee3c32018-10-22 12:52:56 +0300468 }
469 println("Completed ${n * k} actions in $time ms")
470}
471
472// Message types for counterActor
473sealed class CounterMsg
474object IncCounter : CounterMsg() // one-way message to increment counter
475class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg() // a request with reply
476
477// This function launches a new counter actor
478fun CoroutineScope.counterActor() = actor<CounterMsg> {
479 var counter = 0 // actor state
480 for (msg in channel) { // iterate over incoming messages
481 when (msg) {
482 is IncCounter -> counter++
483 is GetCounter -> msg.response.complete(counter)
484 }
485 }
486}
487
Prendota0eee3c32018-10-22 12:52:56 +0300488//sampleStart
Aleksandr Blokh6dc1bd52019-08-25 13:35:08 +0300489fun main() = runBlocking<Unit> {
hadihariri7db55532018-09-15 10:35:08 +0200490 val counter = counterActor() // create the actor
Roman Elizarovd94652f2019-06-01 14:18:42 +0300491 withContext(Dispatchers.Default) {
492 massiveRun {
493 counter.send(IncCounter)
494 }
hadihariri7db55532018-09-15 10:35:08 +0200495 }
496 // send a message to get a counter value from an actor
497 val response = CompletableDeferred<Int>()
498 counter.send(GetCounter(response))
499 println("Counter = ${response.await()}")
500 counter.close() // shutdown the actor
501}
Roman Elizarovd94652f2019-06-01 14:18:42 +0300502//sampleEnd
hadihariri7db55532018-09-15 10:35:08 +0200503```
504
Alexander Prendotacbeef102018-09-27 18:42:04 +0300505</div>
506
Adam Howardf13549a2020-06-02 11:17:46 +0100507> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt).
hadihariri7db55532018-09-15 10:35:08 +0200508
509<!--- TEST ARBITRARY_TIME
510Completed 100000 actions in xxx ms
511Counter = 100000
512-->
513
514It does not matter (for correctness) what context the actor itself is executed in. An actor is
515a coroutine and a coroutine is executed sequentially, so confinement of the state to the specific coroutine
Roman Elizarovd94652f2019-06-01 14:18:42 +0300516works as a solution to the problem of shared mutable state. Indeed, actors may modify their own private state,
517but can only affect each other through messages (avoiding the need for any locks).
hadihariri7db55532018-09-15 10:35:08 +0200518
519Actor is more efficient than locking under load, because in this case it always has work to do and it does not
520have to switch to a different context at all.
521
Inegoebe519a2019-04-21 13:22:27 +0700522> Note that an [actor] coroutine builder is a dual of [produce] coroutine builder. An actor is associated
hadihariri7db55532018-09-15 10:35:08 +0200523 with the channel that it receives messages from, while a producer is associated with the channel that it
524 sends elements to.
Roman Elizarov99c28aa2018-09-23 18:42:36 +0300525
526<!--- MODULE kotlinx-coroutines-core -->
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300527<!--- INDEX kotlinx.coroutines -->
Vsevolod Tolstopyatov167c44e2020-11-30 05:36:23 -0800528
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300529[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300530[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300531[CompletableDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-completable-deferred/index.html
Vsevolod Tolstopyatov167c44e2020-11-30 05:36:23 -0800532
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300533<!--- INDEX kotlinx.coroutines.sync -->
Vsevolod Tolstopyatov167c44e2020-11-30 05:36:23 -0800534
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300535[Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
536[Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
537[Mutex.unlock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/unlock.html
538[withLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/with-lock.html
Vsevolod Tolstopyatov167c44e2020-11-30 05:36:23 -0800539
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300540<!--- INDEX kotlinx.coroutines.channels -->
Vsevolod Tolstopyatov167c44e2020-11-30 05:36:23 -0800541
Roman Elizarov0950dfa2018-07-13 10:33:25 +0300542[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
543[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
Vsevolod Tolstopyatov167c44e2020-11-30 05:36:23 -0800544
Roman Elizarov99c28aa2018-09-23 18:42:36 +0300545<!--- END -->