Kotlin Training Program

DOWNLOAD APP

FEEDBACK

Examples

Even Odd

Let us write a program that prints a list of 5 even and 5 odd numbers parallelly. We’ll need to launch two coroutines, one for each list.

 fun main() {
    runBlocking {
        
        // Coroutine 1 : Even number generator
        launch {
						println("C1 Executing on ${Thread.currentThread().name}")
            repeat(5) {
                println("Even : ${it * 2}")
            }
        }

        // Coroutine 2 : Odd number generator
        launch {
						println("C2 Executing on ${Thread.currentThread().name}")
            repeat(5) {
                println("Odd : ${2 * it + 1}")
            }
        }
    }
}
 

Because of parallel execution, you might expect the output to be a combination of even and odd lines rather than even and then odd lines :

 
C1 Executing on main
Even : 0
C2 Executing on main
Odd : 1
Even : 2
Odd : 3
Even : 4
Odd : 5
Even : 6
Odd : 7
Even : 8
Odd : 9
 

But in reality, the output indicates sequential execution i.e. even and then odd lines :

 C1 Executing on main
Even : 0
Even : 2
Even : 4
Even : 6
Even : 8
C2 Executing on main
Odd : 1
Odd : 3
Odd : 5
Odd : 7
Odd : 9
 

Let’s understand the reason behind this.

Firstly, both coroutines share the same thread - Main because Dispatcher is not specified and runBlocking() uses Main as its default dispatcher. To verify this, we have used Thread.currentThread().name to get the name of the current thread :

 println("C1 Executing on ${Thread.currentThread().name}")
 

As discussed earlier, Main dispatcher provides only a single thread for execution so both coroutines are scheduled on the same thread.

Coroutine 1 is scheduled first because it is written before Coroutine 2. Coroutine 2 will be scheduled only when Coroutine 1 suspends (pauses) or finishes. Turns out, Coroutine 1 does not suspend rather it continously prints 5 lines and then finishes. Thus Coroutine 2 is scheduled only after Coroutine 1 finishes. Hence the sequential execution.

For parallel execution, we need multiple threads. Default & IO dispatcher provide multiple threads for execution. So let’s change runBlocking dispatcher to Default :

 fun main() {
    runBlocking(Dispatchers.Default) {
        
        // Coroutine 1 (C1) : Even number generator
        launch {
            repeat(5) {
                println("Even : ${it * 2}")
            }
        }

        // Coroutine 2 (C2) : Odd number generator
        launch {
            repeat(5) {
                println("Odd : ${2 * it + 1}")
            }
        }
    }
}
 

Although Default dispatcher is used, both coroutines still share the same thread DefaultDispatcher-worker-1 and output is still the same :

 Executing on DefaultDispatcher-worker-1
Even : 0
Even : 2
Even : 4
Even : 6
Even : 8
Executing on DefaultDispatcher-worker-1
Odd : 1
Odd : 3
Odd : 5
Odd : 7
Odd : 9
 

Let’s understand the reason behind this as well.

Printing 5 lines is a very small and extremely fast task for computer as they perform million operations per second. C1 is dispatched immediately on a Default thread. By the time C2 is scheduled, C1 is already finished. As a result of which, the same thread is freed and can be used by C2. Hence, sequential operation is performed.

To be able to witness parallel execution, we have to increase the number of operations (say 500) :

 fun main() {
    runBlocking(Dispatchers.Default) {

        // Coroutine 1 (C1) : Even number generator
        launch {
            println("Executing on ${Thread.currentThread().name}")
            repeat(500) {
                println("Even : ${it * 2}")
            }
        }

        // Coroutine 2 (C2) : Odd number generator
        launch {
            println("Executing on ${Thread.currentThread().name}")
            repeat(500) {
                println("Odd : ${2 * it + 1}")
            }
        }
    }
}
 

Now that number of operations is increased, you can see both coroutines being dispatched on different threads and hence parallel execution :

 C1 Executing on DefaultDispatcher-worker-2
Even : 0
Even : 2
Even : 4
...
Even : 764
C2 Executing on DefaultDispatcher-worker-3
Even : 766
Odd : 1
Even : 768
Odd : 3
Even : 770
Odd : 5
Even : 772
Odd : 7
Even : 774
Odd : 9
Odd : 11
Odd : 13
...
Odd : 117
Even : 780
...
Even : 990
Odd : 119
...
 

Note

Multiples

Now let’s see a different program that performs large number of operations but prints fewer lines. We need to print multiples (first 5) of large numbers (say 12345678 and 23456789). The twist here is that we won’t use normal multiplication to find the multiples because it leads to fewer operations. Rather we will increment and modulo checks which leads to large number of operations.

 fun main() {
    runBlocking(Dispatchers.Default) {

        launch {
            println("C1 Executing on ${Thread.currentThread().name}")
            printMultiples(12345678, 5, '*')
        }

        launch {
            println("C2 Executing on ${Thread.currentThread().name}")
            printMultiples(23456789, 5, '#')
        }
    }
}

fun printMultiples(num: Int, totalCount: Int, char: Char) {
    var count = 0
    var i = 0
    while (count < totalCount) {
        if (i % num == 0) {
            println("$char : $num -> $i")
            count++
        }
        i++
    }
}

/* Output :

C1 Executing on DefaultDispatcher-worker-2
* : 12345678 -> 0
C2 Executing on DefaultDispatcher-worker-3
# : 23456789 -> 0
* : 12345678 -> 12345678
# : 23456789 -> 23456789
* : 12345678 -> 24691356
* : 12345678 -> 37037034
# : 23456789 -> 46913578
* : 12345678 -> 49382712
# : 23456789 -> 70370367
# : 23456789 -> 93827156

Process finished with exit code 0
 */
 

Notice both coroutines being dispatched on different threads leading to parallel execution. Thus, we are able to see interleaving between the executions of both coroutines.

Network Requests

Let’s revisit the Parallel Networks Requests example from Introduction section, with minor modifications.

 fun main() {
    val time = measureTimeMillis {
        runBlocking {
            repeat(5) {
                launch {
                    println("sending R#$it from ${Thread.currentThread().name}...")
                    getAndPrintMathFact(it)
                }
            }
        }
    }
    println("\n=> Parallel execution took $time ms")
}

fun getAndPrintMathFact(num: Int) {
    val response = httpGet("http://numbersapi.com/$num/math")
    println("MathFact($num) = $response")
}

fun httpGet(url: String) = URL(url).readText()

/* Output : 

sending R#0 from main...
MathFact(0) = 0 is the additive identity.
sending R#1 from main...
MathFact(1) = 1 is the multiplicative identity.
sending R#2 from main...
MathFact(2) = 2 is many properties in mathematics.
sending R#3 from main...
MathFact(3) = 3 is the number of spatial dimensions we live in.
sending R#4 from main...
MathFact(4) = 4 is the maximum number of dimensions of a real division algebra (the quaternions), by a theorem of Ferdinand Georg Frobenius.

=> Parallel execution took 2275 ms

 */
 

Notice the output. Even though 5 requests are being sent on separate coroutines, we observe sequential execution i.e. R#2,3,4 is sent only after response of R#1,2,3 is received respectively. Why is this so? Let’s understand.

Firstly, no dispatcher is specified and runBlocking is used so all coroutines are scheduled on Main thread.

As we saw in the Even Odd example, next coroutine starts only after previous one suspends or finishes. In the Even Odd example, coroutine need not suspend due to continous computation. But here, we are performing network request so C1 must suspend after sending request allowing C2 to send request. Similarly, all coroutines must have suspended after sending request leading to parallel execution. But clearly, this is not the case.

The reason being - not all functions in Kotlin can suspend (pause) as required in Network requests. There are special functions that can suspend, intuitively known as Suspend functions. We will learn more about Suspend functions in later sections.

In this example, httpGet() function is not a suspend function so coroutines do not suspend after sending the request. This is also known as Blocking behaviour because execution is blocked after sending the request. Hence, next request can only be sent after previous one completes. Thus, sequential execution.

How do we solve this issue? There are two solutions :

  1. Use suspend function
  2. Create multiple threads, one for each coroutine

We will implement S#1 later. For now, let’s implement S#2 using IO dispatcher to allow multiple threads :

 fun main() {
    val time = measureTimeMillis {
        runBlocking(Dispatchers.Default) {
            repeat(5) {
                launch {
                    println("sending R#$it from ${Thread.currentThread().name}...")
                    getAndPrintMathFact(it)
                }
            }
        }
    }
    println("\n=> Parallel execution took $time ms")
}

fun getAndPrintMathFact(num: Int) {
    val response = httpGet("http://numbersapi.com/$num/math")
    println("MathFact($num) = $response")
}

fun httpGet(url: String) = URL(url).readText()

/* Output : 

sending R#0 from DefaultDispatcher-worker-3...
sending R#1 from DefaultDispatcher-worker-4...
sending R#2 from DefaultDispatcher-worker-5...
sending R#3 from DefaultDispatcher-worker-6...
sending R#4 from DefaultDispatcher-worker-8...
MathFact(0) = 0 is the additive identity.
MathFact(1) = 1 is the multiplicative identity.
MathFact(4) = 4 is the first positive non-Fibonacci number.
MathFact(3) = 3 is the fourth open meandric number.
MathFact(2) = 2 is the smallest and the first prime number, and the only even one (for this reason it is sometimes called "the oddest prime").

=> Parallel execution took 1070 ms

 */
 

Notice all five coroutines being dispatched on different threads, allowing parallel excution.

However, this is a bad approach because all five threads are blocked after sending the request. Moreover, we could have used only one thread as the coroutines can suspend after sending the request, allowing parallelism. We’ll fix this issue using suspend function in later sections.