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

Davio

09/20/2023, 12:21 PM
Hey all, I think I understand coroutines pretty well, but when I think deeper about them, maybe not so much. 😄 I have a question: what exactly happens when blocking code is called from within a suspending function, e.g.
Copy code
suspend fun foo() =
  withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
    blockingCall()
}
I think this code is handled by a thread from the thread pool that is shared with the Default dispatchers pool, which means that if this suspending function was called from another thread, that thread can now be used for something else, correct? But if this code is reached from another suspending function already executing on that Dispatcher or the default dispatcher, I guess it will try to not switch to another thread? And the thread in the pool that is performing the blocking code is blocked I think. If there are by default 64 threads in that pool, does that mean that for instance in a microservice with a REST controller with suspending functions, only 64 requests can be handled in parallel?
d

Dmitry Khalanskiy [JB]

09/20/2023, 12:26 PM
in a microservice with a REST controller with suspending functions, only 64 requests can be handled in parallel
Yes if you're using
<http://Dispatchers.IO|Dispatchers.IO>
directly. The recommended way to handle such tasks is to use a separate view of
<http://Dispatchers.IO|Dispatchers.IO>
that has as many threads as you'd like. Please see https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html
But if this code is reached from another suspending function already executing on that Dispatcher or the default dispatcher, I guess it will try to not switch to another thread?
Maybe it will, but often it won't. It's just a performance optimization to avoid a context switch between threads. A thread that used to do work for
Dispatchers.Default
now will instead do I/O tasks, no big deal.
I think this code is handled by a thread from the thread pool that is shared with the Default dispatchers pool, which means that if this suspending function was called from another thread, that thread can now be used for something else, correct?
There are as many threads involved in
Dispatchers.Default
as there are CPU cores. If all threads dedicated to
Dispatchers.Default
were busy when you make a call to
foo
, our scheduler will know that now, only (CPU cores - 1) threads are doing work for
Dispatchers.Default
, and will allocate another thread for that. Or, yes, as you said, it will pass the work in
foo
to another thread and let this one do other work for
Dispatchers.Default
.
Does this answer the question?
d

Davio

09/20/2023, 12:35 PM
Yes I think so, the only thing remaining is if this function actually gets suspended, what exactly happens. Who decides whether to suspend execution. There is a blocking call waiting to be executed so does that call get paused ?
d

Dmitry Khalanskiy [JB]

09/20/2023, 12:38 PM
No one can ever decide for the function that it should suspend. If there is a suspending call in the function, it may suspend, otherwise it won't.
d

Davio

09/20/2023, 12:41 PM
Okay so if no code inside calls yield it might not actually suspend ever?
d

Dmitry Khalanskiy [JB]

09/20/2023, 12:43 PM
Not just
yield
but also
delay
,
Mutex::lock
,
Channel::receive
,
suspendCancellableCoroutine
, and so on, and so on. There is a plethora of suspending functions. The point is, they have to be explicitly called for a suspension to happen.
d

Davio

09/20/2023, 12:44 PM
Okay I think I understand now, thanks for your help
2 Views