2

Why the output of this code is so unpredictable?

suspend fun main() {
    val job1 = GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    val job2 = GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    val job3 = GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")

    println(job1)
    job1.join()
    println(job1)

    job2.join()
    job3.join()
}

Output:

Hello,
StandaloneCoroutine{Active}@340f438e
World!
World!
World!
StandaloneCoroutine{Completed}@340f438e

I was expecting this as the output:

Hello,
StandaloneCoroutine{Active}@340f438e
World!
StandaloneCoroutine{Completed}@340f438e
World!
World!

Is it some kind of race condition? Or is it because of some way the "continuation" works in Kotlin Coroutine?

1
  • Why do you expect job2 and job3 not run before println(job1)? Commented Jul 28 at 4:28

1 Answer 1

2

This is a borderline race condition: It will probably always execute in this order, but due to its nature I would never rely on it.

The order in which coroutines are executed depends on several factors.

In this case the three coroutines you start (with launch) are scheduled for immediate execution. Since only a single thread is involved (the one that called main, probably the Main thread), they can only start execution when the current thread becomes free. That happens at job1.join() because that is a suspend function, suspending until job1 completes.

Suspension makes the current thread free so another coroutine can occupy it. First job1 is executed, then job2 and job3 - in the order they were scheduled. They all immediately suspend to wait for 1 second. After the second elapsed, they are all scheduled to continue their execution. This should happen in the order job1-job2-job3, but I am actually not sure if this is guaranteed. That doesn't matter in the current situation, though.

When job1 eventually completes, the suspended job1.join() is scheduled to continue. But around the same time, also job2 and job3 finished their one second delay and are also scheduled to continue. This is what makes it prone to a race condition: Depending on the speed of the hardware, the resolution of the internal timer, the overall pressure on the current thread and so on it is not guaranteed that the continuations of job2 and job3 are scheduled before job1.join() continues. There is a high probability that they will be, though, resulting in the output you observe.

As mentioned at the beginning, even with a high probability I would not rely on this always being the case.

Sign up to request clarification or add additional context in comments.

2 Comments

Side point, I know, but sharing anyway. For delay, just like for Thread.sleep() and all similar functions, the only guarantee is that it will sleep at least that long (unless interrupted). After that, it's subject to noisy thread/coroutine scheduling just like anything else. So, definitely no ordering guarantees.
More relevant point: OP uses GlobalScope.launch, this uses the Default threadpool. So it doesn't even depend on the main thread getting freed.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.