I was just checking the behaviour of the Coroutine Dispatchers. So I was trying to understand how many threads each of these Dispatchers can create. To check this, I created a loop statement counting from 1 to 1_000_000.
In each loop iteration, I'm creating a coroutine with Dispatchers.IO, But when I see the output, it is not created 1_000_000 coroutines, and all these are DefaultDispatcher-worker threads, and it always stops execution in the range 40000-50000 randomly.
But when I replaced Dispatchers.IO with Dispatchers.Unconfined, it actually created all the 1_000_000 coroutines without fail and all these are created on Main thread.
So I need some help here to understand why Dispatchers.IO is failed in this case.
Dispatchers.IO:
fun main() {
println("Start of main")
val handler = CoroutineExceptionHandler { _, e -> e.printStackTrace() }
runBlocking {
for (i in 1..1000000) {
CoroutineScope(Dispatchers.IO CoroutineName("Dispatchers.IO")).launch(handler) {
println("${Thread.currentThread().name} task number is :$i")
}
}
}
println("End of main")
}
Dispatchers.Unconfined
fun main() {
println("Start of main")
val handler = CoroutineExceptionHandler { _, e ->
e.printStackTrace()
}
runBlocking {
for (i in 1..1000000) {
CoroutineScope(Dispatchers.Unconfined CoroutineName("Dispatchers.Unconfined")).launch(handler) {
println("${Thread.currentThread().name} task number is :$i")
}
}
}
println("End of main")
}
Thanks in advance
CodePudding user response:
But when I see the output, it is not created 1_000_000 coroutines, and all these are DefaultDispatcher-worker threads, and it always stops execution in the range 40000-50000 randomly.
The reason for this, I believe is that you're not waiting for all child coroutines to complete, and your program exits prematurely.
In the below code, Job
instances are collected in a list named jobs
. After the loop, jobs.joinAll()
causes the execution to wait for all child coroutines to complete.
What is interesting, is the difference in execution time Dispatchers.IO
vs. Dispatchers.Unconfined
.
- Dispatchers.IO: Elapsed time 4.040274590s
- Dispatchers.Unconfined: Elapsed time 959.173375ms
When adding the suspending delay(1)
to the loop, Dispatchers.Unconfined
will change thread from main
to kotlinx.coroutines.DefaultExecutor
.
Dispatchers.Unconfined
executes in strict sequence 1..1_000_000.
Tested on AMD 8-core, Ubuntu 20.04.
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
@OptIn(ExperimentalTime::class)
fun main() {
println("Start of main")
measureTime {
runBlocking {
val jobs = mutableListOf<Job>()
repeat(1_000_000) { index ->
jobs.add(launch(Dispatchers.IO) {
println("${Thread.currentThread().name} task number is :${index 1}")
})
}
/** wait until child coroutines complete */
jobs.joinAll()
println("End of main")
}
}.also { duration -> println("Elapsed time: $duration") }
}