Explicit concurrency

Roman Elizarov
4 min readNov 17, 2018

Concurrency is important in the modern world. A mobile or web application needs to be able to perform a network request and still concurrently update its UI, display animations, and react to user input. Server-side application needs to be able to handle many concurrent requests. What about parallelism? It is useful too, but not that much often. Many successful server-side applications run totally in a single thread without any parallelism, yet scale quite well (think about the whole node.js platform).

Even without parallelism, concurrency is hard and can be a source of subtle timing-dependent bugs. Developers are used to sequential programs that do things one after another, and it does not matter if they code in functional or imperative style. The whole concept of a function call is sequential — you call a function and, after a while, it returns a result that you can use. Look at the following code:

foo(bar())

It is just an expression and it looks very functional, yet it is a good example of a sequential code. Its sequential nature becomes obvious if we rewrite it in a more imperative style, like this:

val x = bar()  // first call bar
foo(x) // then call foo

In “Blocking threads, suspending coroutines” story I’ve shown a difference between regular functions and suspending functions in Kotlin. The latter don’t block a thread, but they still execute sequentially. For example, if bar is suspending, then the above examples are still sequential and foo executes only after bar successfully returns.

Concurrency, on the other hand, is very explicit in Kotlin. You explicitly use a coroutine builder like launch { ... } to initiate concurrent execution of some piece of code with the respect to the rest of the program:

launch {
foo(bar())
}

However, every concurrent coroutine builder in kotlinx.coroutines library is declared as an extension on CoroutineScope interface and launch is no exception. So, if you extract the code that initiates concurrent execution of foo(bar()) into a separate top-level function, then you get the function with the following signature:

fun CoroutineScope.launchFooBar() = launch { 
foo(bar())
}

This function returns immediately and executes foo(bar()) concurrently with the rest of the program that calls launchFooBar.

It leads to the following useful convention: every function that is declared as extension on CoroutineScope returns immediately, but performs its actions concurrently with the rest of the program.

It also explains one of the reasons why runBlocking is NOT an extension function on CoroutineScope.

Following this convention in your own code is very helpful, too. It is easy to show with a counter-example. Consider the function with the following signature that you could stumble upon somewhere in the code you work with:

suspend fun CoroutineScope.obfuscate(data: Data)

What could this function do? It is a suspending function, so we know, by definition, that it could suspend execution of a coroutine. But it is also defined as an extension on CoroutineScope, so it could launch a new coroutine to do something that would work concurrently with the rest of the program. Why would it suspend execution then? What parts of its operation are performed concurrently? We cannot tell without reading its documentation and/or source code. On the other hand, if we choose to declare it as a suspending function:

suspend fun obfuscate(data: Data)

Then, by convention, we know that it does its obfuscate thing without blocking the caller and returns to the caller when it’s done.

Alternatively, if we choose to declare it as a CoroutineScope extension:

fun CoroutineScope.obfuscate(data: Data)

Then, by convention, we know that it returns immediately without blocking the caller and starts doing its obfuscate thing concurrently with the rest of the program.

As a rule of thumb, you should prefer suspending functions, since concurrency is not a safe default to have. Anyone with a suspending function at hand can explicitly use launch { ... } to initiate its concurrent execution.

Global scope

It is tempting to write GlobalScope.launch { ... } when you need to start doing something concurrently but your code is not inside any CoroutineScope. It is as easy as submitting a new task to the global background thread pool. Resist this temptation. It is not hard to lose track of concurrent activities you have, run out of resources and/or introduce memory leaks this way (see more in “The reason to avoid GlobalScope”).

Explicit use of CoroutineScope lets you contain, delimit and keep track of all the concurrent operations in your application and, importantly, tie them to the lifecycle of your application entities. But that is another story (see “Coroutine Context and Scope”).

Further reading

The reasoning that led to introduction of the CoroutineScope and structured concurrency in Kotlin coroutines was explained in “Structured concurrency” story. It also shows how suspending functions can perform multiple concurrent operations while still maintaining invariant that they return to the caller only when they’ve done doing their thing.

If you are interested in a formal definition of concurrent execution and how it is different from sequential, then read my totally approachable introduction to this hard theoretical problem in this story on concurrency I wrote a while ago.

More details on GlobalScope can be found in the reason to avoid GlobalScope story.

--

--

Roman Elizarov

Project Lead for the Kotlin Programming Language @JetBrains