Во время моего вводного разговора о сопрограммах я показываю этот пример, который пытается создать 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 тыс. Сопрограмм не займет много времени, и все они могут начать работать одновременно - сопрограммы действительно , действительно легкий. Уф! Жизнь идет.