Such concurrency! Many threads! Wow!

During my introductory talk on coroutines I show this example that tries to create 100k threads, each one printing a dot after a second delay:

fun main(args: Array<String>) {
val jobs = List(100_000) {
thread {
Thread.sleep(1000L)
print(".")
}
}
jobs.forEach { it.join() }
}

I claim that running this code produces an error. However, if you actually run this code on macOS High Sierra it does not crash, but prints a sequence of dots on the console.

...............................................................

Wow! Have they finally optimized something so that we don’t need coroutines? Can we simply use threads to scale our applications to hundreds of thousands of concurrent operations?

No. If we time this code (measureTimeMillis is a nice little gem in the Kotlin standard library), then we see that it completes (on my machine) in around 50 seconds. Threads are being started at a rate of approximately 2k per second, so with a second delay in each thread we don’t really have more than 2k threads running at the same time here. Change this code to use a ten second delay with Thread.sleep(10000L) and you get:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

However, run the same code with 100k coroutines and use delay(10000L) to suspend a coroutine for ten seconds and it runs without errors:

fun main(args: Array<String>) = runBlocking {
val
jobs = List(100_000) {
launch {
delay(10000L)
print(".")
}
}
jobs.forEach { it.join() }
}

Moreover, it waits for 10 seconds, as expected, and then prints 100k dots almost instantly, completing in slightly over 10 seconds, which means that creating 100k coroutines does not take a lot of time and they can all start working concurrently — coroutines are really, really light-weight. Phew! Life goes on.