https://kotlinlang.org logo
#coroutines
Title
# coroutines
l

Luis Munoz

08/01/2019, 8:25 PM
Is this code thread safe? If update() is called from different threads it will use the context of Example: CoroutineScope so it will never be inside update() from two different threads correct?
g

groostav

08/01/2019, 8:26 PM
Nothing as you've written here gives you sequential access to
update()
l

Luis Munoz

08/01/2019, 8:26 PM
how do I modify it so update can only be run sequentially
if the context was single threaded then obvioulsy only 1 thread can access it
d

Dominaezzz

08/01/2019, 8:27 PM
Use
Mutex
or AtomicInt .
g

groostav

08/01/2019, 8:28 PM
you could linearize with a dispatcher or with a mutex:
Copy code
class Example : CoroutineScope {
  //extending coroutine scope doesnt get you anything implicitly. It does make `launch()` and `async` et all easier to use

  val mutex = Mutex()

  suspend fun update() {
    mutex.withLock {
      count++
    }
  }
}
l

Luis Munoz

08/01/2019, 8:28 PM
Oh I must have a misunderstanding, I thought only 1 context can be run within 1 thread at any given time
g

groostav

08/01/2019, 8:28 PM
alternatively, if your current coroutine scope uses a dispatcher that is serial (eg,
Dispatchers.Main
,
Dispatchers.JavaFx
, etc)
Copy code
suspend fun update() = withContext(this.coroutineContext){
  count++
}
l

Luis Munoz

08/01/2019, 8:29 PM
Isn't there a rule or something that says a context cannot be executing from two threads at the same time?
g

groostav

08/01/2019, 8:30 PM
I need an emoji thats got a screwed up expression.
😅 1
l

Luis Munoz

08/01/2019, 8:31 PM
vertx has that, I guess I thought coroutines did the same thing
d

Dominaezzz

08/01/2019, 8:31 PM
There is such a rule but your context is copied.
g

groostav

08/01/2019, 8:31 PM
no, and infact
Dispatchers.Default
will give you highly parallel behaviour by default. If thats part of your context you will find it running in parallel on many different threads. I will say that koltin coroutines typically offer sqeuential concurrent behaviour by default. This is easier to leverage if you avoid state machiens (eg
update()
) instead favouring channels.
@Luis Munoz can you tell us a bit more about the use case? Is this for a UI framework or some kind of appserver?
l

Luis Munoz

08/01/2019, 8:34 PM
trying to convert code that uses thread locals
g

groostav

08/01/2019, 8:35 PM
well in that case a huge disclaimer about mutex is that it is not reentrant, meaning
mutex.withLock { mutex.withLock {}}
will deadlock
l

Luis Munoz

08/01/2019, 8:38 PM
@Dominaezzz do you know where in the documentation it talks about that rule of not running two context at the same time?
g

groostav

08/01/2019, 8:40 PM
using a serial dispatcher (IE, a
Executors.newSingelThreadedExecutor().asCoroutineDispatcher()
in your scope's context and then
withContext
--ing every entry point will give you single-threaded concurrency, which might be helpful.
l

Luis Munoz

08/01/2019, 8:43 PM
yes that's one way, but I'm wondering if we can be done by making it so it doesn't copy the context
and you just hold the context and launch from it and even it switches threads it can guarantee you won't execute that code from multiple threads at the same time
d

Dominaezzz

08/01/2019, 8:45 PM
Not quite docs but close enough.
Use atomics.
😱 1
l

Luis Munoz

08/01/2019, 8:49 PM
thanks guys. For some reason I just remember reading about how context can't be executed at the same time and thought I could use that fact to do some concurrency stuff but I can't find where I read that etc.
g

groostav

08/01/2019, 8:50 PM
yeah I mean, the closest I can think of is that a coroutine context always defines the dispatcher, and the dispatcher will either allow for parallelism or it wont.
d

Dominaezzz

08/01/2019, 8:51 PM
Contexts belong to a single coroutine. Coroutines run synchronously.
l

Luis Munoz

08/01/2019, 8:57 PM
and a context cannot be reused, right?
d

Dominaezzz

08/01/2019, 8:59 PM
Hmm, that's a tricky one. I'm not sure.
g

groostav

08/01/2019, 11:32 PM
I dont think you guys understand coroutine contexts. They're immutable. They're a collection of things used in support of running coroutine. Another way to think about it is if you were to parameterize
Executors
, such that rather than have
Executors.newSingleThreadedExecutor
and
Executors.forkJoinPool
you would have
Executors.make(context = SingleThreaded)
. You can absolutely take a coroutine context and re-use it by doing
Copy code
val context = EmptyCoroutineContext + MySpecialContext

GlobalScope.launch(context) { /* job1 */ }
GlobalScope.launch(context) { /* job2 */ }
In this way, both job1 and job2 have the same context. The purpose of scope (as opposed to context) is mostly to try and employ a parent-child relationship to try and keep management a little easier and error states a little cleaner.
l

Luis Munoz

08/07/2019, 7:52 PM
@groostav that will not re-using the exact same context. If you do
if you look at coroutineContext it is slightly different, because launch copies the context and creates a unique new one
d

Dominaezzz

08/07/2019, 8:27 PM
Also, they'll have different `Job`s.
Contexts aren't immutable. It's a key value store, where the keys are immutable but the values aren't.
g

groostav

08/07/2019, 11:58 PM
you are right about this.
launch
does indeed give you a new coroutine context with a new (parent) job in it.
Context
is a key value store. The implementation of parent-child'ing in jobs relies on a mutable
Job
implementation. To my knowledge all other coroutine context elements are either stateless or immutable.
If you want thread-isolation on a
suspend fun
method there is nothing in a coroutine's context that will help you directly. You can get single-threaded behaviour by changing
Dispatchers
. You can use
mutex
to acquire a lock --you could even put it in your coroutine context to make it a reentrant lock.
4 Views