Coroutines and threads

An instance of the Thread class represents a native thread in the corresponding operating system when a program is running. This means that each instance of the Thread consumes memory for its stack and needs time to be initialized. If you are familiar with multithreaded programming, you know that switching between the contexts of threads is a pretty expensive operation, which is why it makes no sense to invoke short-term tasks in a separate thread.

In Kotlin, a coroutine is a pure language abstraction. Coroutines refer to objects in the memory heap and switching between coroutines doesn't involve operating system kernel operations. You can use a coroutine in the same way as a thread. This means that a coroutine contains a call stack function and stores local variables.

The amount of threads that can be executed in parallel depends on how many logical cores are currently available. How many coroutines can be executed in parallel depends on how many available, running threads are used by coroutine contexts. Let's look at the following example:

fun main(args: Array<String>) = runBlocking<Unit> {
val parentJob = Job()
(0..10_000)
.forEach { launch(parent = parentJob) { println("Thread name: ${Thread.currentThread().name}") } }
parentJob.joinChildren()
}

The output looks like this:

 Thread name: ForkJoinPool.commonPool-worker-4
Thread name: ForkJoinPool.commonPool-worker-2
Thread name: ForkJoinPool.commonPool-worker-4
Thread name: ForkJoinPool.commonPool-worker-7
Thread name: ForkJoinPool.commonPool-worker-1
Thread name: ForkJoinPool.commonPool-worker-5
Thread name: ForkJoinPool.commonPool-worker-3
Thread name: ForkJoinPool.commonPool-worker-6
Thread name: ForkJoinPool.commonPool-worker-3
Thread name: ForkJoinPool.commonPool-worker-5

The following diagram shows how threads may share the available CPU time:

In turn, coroutines share the available thread time:

When we start a new coroutine, it doesn't matter that we create a new thread because a new coroutine can use a pre-existing  thread from a pool. The launch function uses CommonPool as a default argument for coroutine context, with a thread pool that calculates the amount of threads using the following method:

private val parallelism = run<Int> {
val property = Try { System.getProperty(DEFAULT_PARALLELISM_PROPERTY_NAME) }
if (property == null) {
(Runtime.getRuntime().availableProcessors() - 1).coerceAtLeast(1)
} else {
val parallelism = property.toIntOrNull()
if (parallelism == null || parallelism < 1) {
error("Expected positive number in $DEFAULT_PARALLELISM_PROPERTY_NAME, but has $property")
}
parallelism
}
}

For my computer, this value is seven. However, this doesn't mean that seven coroutines are running in parallel. Let's set up a breakpoint:

The debugger window looks as follows:

As you can see, we have a pool with seven threads, but several of them have the WAIT status because the cores are busy with other threads.

Functions such as runBlocking or launch are called coroutine builders, and these take the parent: Job? parameter. In the preceding example, we passed the parentJob variable to invoke the joinChildren method. As a result, the main thread waits until all coroutines are complete.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.234.188