Hi all, I found this statement of the official <do...
# coroutines
h
Hi all, I found this statement of the official docs: Coroutines can be executed parallelly using a multi-threaded dispatcher like the Dispatchers.Default. It presents all the usual parallelism problems. The main problem being synchronization of access to shared mutable state. Some solutions to this problem in the land of coroutines are similar to the solutions in the multi-threaded world, but others are unique. Does it basically mean coroutine share the same problems with thread when it comes to concurrent operations (concurrency) and accessing shared-resources, also does it apply to all kinds of
Dispatcher
? Thanks in advance
j
If you run your coroutines on a multi-threaded dispatcher (any dispatcher that uses a pool of more than one thread), then multiple coroutines can run in parallel in different threads. Since you're effectively running code in multiple threads in parallel, you are doing multithreading and you need to be careful about concurrent access to shares mutable state. You might want to protect resources with mutexes and things like that.
It doesn't apply to all dispatchers, only dispatchers backed by multiple threads. For example,
Dispatchers.Default
,
<http://Dispatchers.IO|Dispatchers.IO>
, custom dispatchers created from JVM
ExecutorService
, etc.
Note that, if a bunch of coroutine all run in the same single-threaded dispatchers (like
Dispatchers.Main
), or on multiple dispatchers backed by the same thread, then the problem doesn't occur and you don't need special synchronization between these coroutines. That said, multiple coroutines running on different dispatchers, even single-threaded, will need synchronization because they effectively run on different threads.
u
I’d say it became even harder to judge if your code is racy. Even on a single threaded dispatcher, your code can be interleaved with other code at suspension points. So with coroutines, atomicity on a single threaded dispatcher depends on wether and where you suspend in between accessing your shared mutable state.
And as always, your single threaded code is not safe, unless all access happens from that same, single thread.
h
thanks y'all!
c
Note that this is only if you write the same kind of code as you would with threads, but this isn't usually what we do. The root problem is "mutable data accessed by multiple concurrent things", but you can solve that in multiple ways. The simpler way is to only access immutable stuff. No mutability, no risks of corruption. Other than that, another way is to avoid data being accessed by different coroutines. Since coroutines are very cheap, we can create a one that is responsible for accessing data, and sending it back and forth to other people. Usually, we keep as much data as possible as local variables to a coroutine, which means it can't be accessed by any other, and thus is safe.
👍 1
h
@CLOVIS I've never needed to actually concern about concurrency up until now so I don't really have the context to process the solution of only accessing immutable stuff 😅 Can you give an example of how it's a possible solution? Since I do know that deadlock and race condition are very prevalent in the Java world, but if the solution is that straight-forward why there are many not applying it... Thanks in advance btw
c
It's difficult to give a broad example like this… If you had a specific situation, I could give more specific advice on that
u
if the solution is that straight-forward why there are many not applying it
Because it requires the right architecture. I.e.if you have (come up) with a solution using mutable state, you cannot transform it into an immutable state solution with some local changes. There is one important quick win with coroutines compared to threads. Coroutines are sequential! That means, even when you switch threads with
withContext
, your whole coroutine behaves like single threaded. (It can still race with other coroutines though). In below example, there is no risk with regards to x, even though x is mutable and x is shared between main thread and a thread from the io dispatcher. Coroutines have “happens before” guarantees, just as regular code, even when switching threads.
Copy code
var x: Int = 0
launch(Dispatchers.Main) {
  x = x + 1
  withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
    x = x + fetchNumberFromServer()
  }
  x = x - 1
}
h
thanks y'all. One last question though, does using immutable state always preferable compared to using mutable in concurrency environment, is there any other drawback of this approach other than memory? Actually I just realised I asked the same question as before...
c
Yes, immutable is always preferable. Immutable can actually use less memory because you can share instances (for example, did you know
emptyList()
is free because it always returns the exact same instance? It never creates a new list). If you have Effective Java, all the points mentioned there apply to Kotlin as well.
h
I don’t have Effective Java, but will pick it up since you mentioned it, and I didn’t know that emptyList() is used like that... Thanks for the advice!
c
Effective Java is a great book, but not all of it applies to Kotlin. Still, quite a lot applies, in particular the parts on immutability. I'm writing a blog post series with the differences, but if you want the common points you'll have to actually read the book
👍 1