Waitgroups in Kotlin

In Kotlin, we can use coroutines to achieve concurrent execution. To wait for multiple coroutines to finish, we can use a CountDownLatch.

import kotlinx.coroutines.*
import java.util.concurrent.CountDownLatch
import kotlin.random.Random

// This is the function we'll run in every coroutine.
suspend fun worker(id: Int) {
    println("Worker $id starting")
    // Delay to simulate an expensive task.
    delay(1000)
    println("Worker $id done")
}

fun main() = runBlocking {
    // This CountDownLatch is used to wait for all the
    // coroutines launched here to finish.
    val latch = CountDownLatch(5)

    // Launch several coroutines and decrement the CountDownLatch
    // counter for each.
    repeat(5) { i ->
        launch {
            try {
                worker(i + 1)
            } finally {
                latch.countDown()
            }
        }
    }

    // Block until the CountDownLatch counter goes back to 0;
    // all the workers notified they're done.
    latch.await()

    // Note that this approach has no straightforward way
    // to propagate errors from coroutines. For more
    // advanced use cases, consider using structured concurrency
    // with coroutineScope or supervisorScope.
}

To run the program:

$ kotlinc -cp kotlinx-coroutines-core-1.5.2.jar WaitGroups.kt -include-runtime -d WaitGroups.jar
$ java -jar WaitGroups.jar
Worker 3 starting
Worker 1 starting
Worker 5 starting
Worker 2 starting
Worker 4 starting
Worker 3 done
Worker 1 done
Worker 5 done
Worker 2 done
Worker 4 done

The order of workers starting up and finishing is likely to be different for each invocation.

In this Kotlin version:

  1. We use coroutines instead of goroutines. Coroutines are Kotlin’s way of handling concurrent operations.

  2. Instead of WaitGroup, we use CountDownLatch. It serves a similar purpose of waiting for multiple operations to complete.

  3. The worker function is marked as suspend to indicate it can be paused and resumed.

  4. We use runBlocking to create a coroutine scope that blocks the main thread until all coroutines complete.

  5. Instead of go func(), we use launch to start new coroutines.

  6. We use delay instead of time.Sleep to pause execution without blocking the thread.

  7. Error handling in coroutines is typically done using structured concurrency with coroutineScope or supervisorScope, which provides more robust error propagation than this simple example.

This example demonstrates how to manage concurrent operations in Kotlin using coroutines and CountDownLatch, achieving similar functionality to Go’s WaitGroups.