Is this code thread safe? If update() is called fr...
# coroutines
l
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
Nothing as you've written here gives you sequential access to
update()
l
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
Use
Mutex
or AtomicInt .
g
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
Oh I must have a misunderstanding, I thought only 1 context can be run within 1 thread at any given time
g
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
Isn't there a rule or something that says a context cannot be executing from two threads at the same time?
g
I need an emoji thats got a screwed up expression.
😅 1
l
vertx has that, I guess I thought coroutines did the same thing
d
There is such a rule but your context is copied.
g
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
trying to convert code that uses thread locals
g
well in that case a huge disclaimer about mutex is that it is not reentrant, meaning
mutex.withLock { mutex.withLock {}}
will deadlock
l
@Dominaezzz do you know where in the documentation it talks about that rule of not running two context at the same time?
g
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
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
Not quite docs but close enough.
Use atomics.
😱 1
l
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
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
Contexts belong to a single coroutine. Coroutines run synchronously.
l
and a context cannot be reused, right?
d
Hmm, that's a tricky one. I'm not sure.
g
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
@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
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
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.