Asynchronous programming in Kotlin: dissecting coroutines

Sinch
Sinch Blog
Published in
6 min readApr 15, 2021

Who never had problems with thread starvation, who never went wrong by blocking the main thread or who never got lost in the famous Callback Hell?

Asynchronous programming is one of the big issues of the moment and is gaining more and more importance, both in the area of ​​Back-End and Front-End.

We still use Java a lot here at Movile and we believe that the language competition solution is difficult to use (and we are not even commenting on the debugging part of it all).

In addition, Java does not encourage reactive programming, let alone non-blocking IO. We have had some advances with Java 8 and its “” “functional” “” interfaces, but they are still far from ideal.

How do we live with that then?

First, let’s understand why starting threads on impulse is a bad practice in general:

Context changes are excessively costly. In order to perform a context switch, a few steps are necessary:
- save a pointer from the current instruction
- save the CPU register (there are cases where this step is not necessary)
- change the execution stack (this requires at least one write operation and some read operations)

It may seem very simple, but in an overloaded system, all this permutation of threads can mean a series of those beloved timeout errors that we all love.

Threads require blocking. While blocking itself is relatively inexpensive, a thread competing for OS resources will require additional context changes, since, if put on hold, it is unable to continue its execution. And it is not uncommon for a thread that got a resource to have to be put on hold a few clocks later because it requires the lock of another resource.

Dependent OS. One of the thread definitions is “an OS-level primitive used for scheduling”. OS schedulers are designed for general use and schedule different types of programs. But a thread running an audio encoder and a thread receiving data packets from your network behave differently and the same scheduling algorithm may not be optimal for both.

Excessive memory usage. Each thread has its own stack, which needs to be allocated at the time of its creation. The number of threads that we can create in an application is limited by the memory made available by the OS for that process. The Oracle FAQ even addresses this issue:

1) My application has a lot of threads and is running out of memory, why?

A. You may be running into a problem with the default stack size for threads. In Java SE 6, the default on Sparc is 512k in the 32-bit VM, and 1024k in the 64-bit VM.
On x86 Solaris/Linux it is
320k in the 32-bit VM and 1024k in the 64-bit VM.
On Windows, the default thread stack size is read from the binary (java.exe). As of Java SE 6, this value is
320k in the 32-bit VM and 1024k in the 64-bit VM.
You can reduce your stack size by running with the -Xss option.
64k is the least amount of stack space allowed per thread.

But do we have other alternatives?

Of course! Go presents a very well designed solution for asynchronous computing with its Goroutines and Channels; C # with async / await; Clojure with your core.async and the Node.js Event Loop. And inspired mainly by the first two, we have, finally, Kotlin with its coroutines.

Let’s talk about Kotlin

Along with version 1.1, Kotlin introduced the experimental coroutines feature. Of course, we wanted to test it here at Movile.

JetBrains likes to describe them as “light-weight threads” (in the same way that Go treats its Goroutines).

Coroutines provide a way to execute code asynchronously, without most of the overheads found in Java Threads and in much the same way as sequential code that we are all used to. And all of this is abstracted in a simple way for you.

They are also not mapped to native threads, that is, they do not have as many limitations as the notorious threads.

But calm down, don’t throw away your copy of “Java Concurrency in Practice”!

Coroutines represent operations that wait for something most of the time. For example: HTTP requests, writing and reading to a database… Mostly: IO bound operations

Threads are still a good choice for tasks that require processing power (CPU bound). Examples: calculating hashes, rendering 3D graphics, transcoding videos …

The magic behind

Coroutines (conceptually speaking), instead of blocking the thread on which they are executed, can suspend an operation at any time so that it can be completed later (this can happen even on another thread).

Functions that can be suspended are called “suspending functions” and make up the core of the coroutines API.

Suspending a coroutine basically tells the thread on which it is executed: “hey, I’m waiting for some things here to be able to finish these operations, so you can move on and move on to the next one! We talk later”

This means that there is no need to have stuck threads waiting, just consuming resources. On the contrary, you can create 100,000 coroutines in a single thread. [1]

But of course, like the scientists we are (or want to be), we don’t believe in magic.

How is all this implemented? The answer is quite simple: Continuation-Passing Style (CPS) or in its less formal name, Callbacks.

A practical example

Let’s say I want to write a suspending function that returns information about a user from my database:

How is this seen from the point of view of the JVM?

Object queryUser (int id, Continuation <User> cont) {…}

But what is this Continuation that came from beyond?

A Callback interface:

So does it mean that every code I write in a coroutine will turn into a callback?

Yes and in a very similar way to the way that C # implements all of this.
Let’s continue our example by imagining that we want to send an email to this user of ours and if this is not possible, we send an sms as a fallback:

What does our sendMessageToUser look like under the hood?

The code above is fictitious, but it represents how coroutines are implemented.

Some of the main points:

The conversion from “sequential code” (direct style) to callback is done via Labels. This facilitates, for example, the conversion of loops.
Callbacks are controlled by a state machine instead of creating several new functions. This removes the overhead of creating anonymous functions for all calls.

Sharing states

Well, you have two choices: the “classic” way, better known as “Shared Mutable State” or the way encouraged by Kotlin:

“Share by Communication”
This approach is based on the principles presented by the Actors of Erlang (or if you are more used to the JVM: Akka):

No shared states
Light processes
Asynchronous messaging
Mailboxes buffer incoming messages
Mailbox processing with pattern matching
In short: a state that should be shared between coroutines is encapsulated and managed by an Actor (actor).

An actor is a combination of a state, a behavior and a Channel for sending and receiving messages. He is in charge of changing the state according to the message received.

In Kotlin, a simple actor can be written as a function, but for more complex states it is recommended to use a specialized class:

As these messages are received sequentially, processed one by one and there is no need to change context (we do not change threads!), The Actors model solves the problem of sharing states without the lock / block overheads for synchronization.

The application continues its flow of execution based “only” on the communication between its actors.

Future / Deferred

A Future defines a value that is initially unknown, as it will be defined in a processing that is not over yet.

In javascript (ECMAScript) we have this as Promises; Scala introduces us to both Futures (read-only) and Promises (writable) and this is also nothing new for Java programmers:

We have CompletableFutures since version 8;
Guava’s ListenableFutures;
The Observables of RxJava;
and a whole bunch of implementations via other external libs.
Kotlin prefers to use the broader term: Deferred. But instead of defining his own Future API, he proposes to unify the use of JVM Futures by hiding the implementation and defining integrations with most of the most popular libs in Java.

How is it done?

Simple right?

It also defines an extension function to mark a Future as suspended:

Finishing…

None of this is new, both in terms of concepts and in terms of how it is implemented. However, Kotlin brings this to us in an abstract and easy way.

For people who can’t leave the JVM and are looking for solutions presented by other languages ​​for event-oriented programming and non-blocking IO in an interoperable way and with less headache, this experimental API may be a good option.

Why not give it a try?

--

--

Sinch
Sinch Blog

Follow us to stay connected to our minds and stories about technology and culture written by Sinchers! medium.com/wearesinch