4

I'm trying to understand how Kotlin coroutines work and why they are said to be more efficient than threads.

As I understand it so far:

  1. when a suspend function is paused, under the hood it voluntarily gives control back to the caller, which in turn does the same thing until we reach some kind of "root" call that manages everything, e.g. the coroutine scheduler
  2. the scheduler checks which paused functions are ready to resume and tells them to continue
  3. it is basically something like a queue with a thread pool

Is this roughly correct?

My concern is:

If inside a suspending function we call a classic Java API that blocks for I/O, it won't automagically make the code more efficient if we simply wrap it in a coroutine, because it would not cooperate with the scheduler, right? Not to mention CPU-intensive tasks.

i.e. it's not like the Node event loop where all I/O is asynchronous by default and is off-loaded to epoll or something similar, it's just a big thread pool under the hood?

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
szx
  • 6,433
  • 6
  • 46
  • 67
  • 1
    See [here](https://stackoverflow.com/q/66412090/5133585) for how you can wrap a blocking code in a coroutine. – Sweeper Aug 07 '23 at 10:02

1 Answers1

2
  1. It gives control back to the thread (not sure exactly what you mean by the caller). To support this, all the threads that are involved in running coroutines have loops in them so finite pieces of work can be submitted to them.

  2. That's true for delay(), but I think with most suspend functions it's the other way around. They inform the scheduler (via the Continuation they are passed) that they're ready to resume.

  3. Yes, it's like a thread pool. In this case, the pools are called "Dispatchers".

When you wrap asynchronous or blocking Java code in a suspend function or withContext in order to use it in a coroutine, this does not provide any efficiency benefit. It just makes it possible to use it in a coroutine. The benefits are structured concurrency and synchronous (sequential, easy-to-read and reason about) code.

  • In the case of a blocking function wrapped in withContext, it will run blocking code on a thread that's part of whichever Dispatcher you've put in the context.
  • In the case of an asynchronous callback-based call wrapped in suspendCancellableCoroutine, the asynchronous work is done on the same thread the API would be using with or without coroutines. Usually that's some thread pool provided by the API.

Possibly the official documentation about coroutines overemphasizes their performance benefits over using threads directly and this leads to confusion, since even without coroutines we wouldn't normally be using threads directly for these sorts of tasks. We'd be using APIs, third party schedulers like Rx, or java.concurrent.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154