Во время моего вводного разговора о сопрограммах я показываю этот пример, который пытается создать 100 тысяч потоков, каждый из которых печатает точку после второй задержки:

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

Я утверждаю, что запуск этого кода вызывает ошибку. Однако, если вы действительно запустите этот код в macOS High Sierra, он не выйдет из строя, а напечатает последовательность точек на консоли.

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

Ух ты! Они наконец что-то оптимизировали, чтобы нам не нужны были сопрограммы? Можем ли мы просто использовать потоки для масштабирования наших приложений до сотен тысяч одновременных операций?

Нет. Если мы рассчитаем время этого кода (measureTimeMillis - это приятная жемчужина в стандартной библиотеке Kotlin), то мы увидим, что он завершается (на моей машине) примерно за 50 секунд. Потоки запускаются со скоростью примерно 2 КБ в секунду, поэтому с секундной задержкой в ​​каждом потоке у нас действительно не может быть более 2 КБ одновременно выполняемых потоков. Измените этот код, чтобы использовать десятисекундную задержку с Thread.sleep(10000L), и вы получите:

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

Однако запустите тот же код со 100 тыс. Сопрограмм и используйте delay(10000L), чтобы приостановить сопрограмму на десять секунд, и она будет работать без ошибок:

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

Более того, он ожидает 10 секунд, как и ожидалось, а затем почти мгновенно печатает 100 тыс. Точек, что означает, что создание 100 тыс. Сопрограмм не займет много времени, и все они могут начать работать одновременно - сопрограммы действительно , действительно легкий. Уф! Жизнь идет.