«Loom» означает «ткацкий станок» — так назывался проект по добавлению асинхронности в джаву. Тяжёлые системные потоки заменили легковесными виртуальными потоками. Потоки и нити в английском называются одинаково - thread - отсюда название.

Проект успешно внедрили пару лет назад, и я, к сожалению, это полностью пропустил. Основная причина, конечно - что я в принципе не ожидал от джавы никаких прорывов (и оказался на 100% неправ!) Ну и, как бэкенд-разработчик на питоне, я не очень-то слежу за развитием JVM-языков.

Но исправляюсь: Project Loom - это блестящая идея. Учитывая, что его внедрили совместимым образом, и почти не было ломающих изменений. Я считаю, это оптимальная реализация асинхронности для высокоуровневого серверного языка.

Уже почти все использующие JVM языки переехали на новую версию JVM, и, таким образом, на проект Loom: деваться им некуда. Так что, есть надежда, что это изменение откроет новую главу для некоторых из них. Есть также предположение, как оно может повлиять на скриптовые языки, такие как питон. Гипотеза только.

Также, вы узнаете, почему Гвидо ван Россум не ошибся, когда добавлял async/await в питон, а Мацумото - создатель Ruby - тоже угадал, когда, наоборот, отказался от async/await.

Проект Loom стартовал в далёком 2018 году и делался долгих 5 лет - зарелизился в 2023-м. То есть, будь я джава-разработчиком, то терпения дождаться у меня бы точно не хватило. Ну что же, в так называемых "enterprise-grade" программных продуктах всё происходит не быстро.

Тем не менее, принципиальная идея довольно простая: мы хотим иметь возможность создавать большое количество (относительно долгоживущих) виртуальных процессов, которые будут маппиться на значительно меньшее число системных процессов. При этом, внутри одного виртуального процесса всё будет происходить строго последовательно. Последний факт - то есть, то, что виртуальный поток очень напоминает обычный - позволяет переписать всю стандартную библиотеку так, чтобы она работала для виртуальных потоков. Так, конечно, и сделали (совместимость - наше всё). Поддержку обычных потоков, намертво привязанных к системным, конечно, тоже оставили.

Подробнее о том, как это работает, можете посмотреть здесь (если хотите пропустить лирику, можете смотреть со второй половины). Правда, на видео автор в конце говорит, что всё это хорошо, только в го всё это давно есть и даже гораздо лучше. Что, на мой взгляд, не так однозначно: всё-таки, в Loom значительно более простая реализация: например, там нет каналов (по которым общаются горутины в го) и, по сути, не предусмотрена так называемая "fine-grained concurrency", когда виртуальные потоки могут произвольным образом ветвиться и потом вновь сливаться.

Что касается fine-grained concurrency, то её поддержку обещают почти все языки и рантаймы, которые поддерживают асинхронность. Но сдерживают они это обещание с разной степенью успеха: в общем случае, это довольно сложная задача, и появляется куча дополнительных проблем. Например, отмена и откат операций. В том же го, например, горутину нельзя убить - она может только завершиться сама. Поэтому в горутину часто передаётся "контекст", с помощью которого она может узнать, что ей пора завершаться. Способ не гибкий, но он укладывается в философию го, где все ошибки возвращаются и явно обрабатываются.

Делать вещи последовательно фундаментально проще, чем делать их параллельно - это факт. В этом смысле, мне нравится проект Loom тем, что это как бы наибольший общий знаменатель, наиболее простая система. Которая более чем годится для высокоуровнего серверного языка. Вот, язык го, например, позиционирует себя как "почти системный" ("почти" - из-за присутствия сборщика мусора), и такой аргумент, что "для высокоуровнего языка сойдёт", для него не годится.

Как я уже писал, виртуальные потоки сейчас поддерживают почти все JVM языки. И из них, конечно, основной - это Kotlin (кроме самой джавы). И тут небольшая проблема возникает: дело в том, что в котлине есть свои корутины. В документации, конечно, поспешили объяснить, что эти 2 технологии решают разные проблемы: проект Loom позволяет эффективно запускать "legacy" код с минимальными изменениями, а корутины в котлине предназначены для написания "свежего" кода. Но для меня это объяснение неубедительно: мне и свежий код удобно запускать в последовательном виртуальном потоке, и мне этого хватит в большинстве случаев. Другое дело, что Kotlin работает не только на JVM, а, например, и на андроиде, и там от корутин никуда не деться. Но что касается, например, фреймворка ktor - моё убеждение - что корутины там по дефолту не нужны.

Говоря о JVM языках, нельзя не упомянуть Scala. Помню, там вначале была популярна библиотека Akka и идея об акторах. Но они как-то не прижились: победил прагматичный подход по типу async/await. Это всё - потому, что сложно представить актор без легковесного отдельного потока для него. А теперь, с проектом Loom, эта идея может получить новое развитие. Хотя, лично меня такие чересчур scalable системы не очень интересуют.

И в заключение - о том, как проект Loom можно использовать для скриптовых языков, таких как Python и Ruby. Но вначале хочу сказать об одном важном обстоятельстве, которое сделала возможной реализацию проекта Loom. Это то, что JVM довольно сильно изолирована от нативного кода, подключать нативные библиотеки там не принято. Те немногие нативные расширения, которые были, после переезда на Loom с хорошей вероятностью сломались, но сообщество не очень это почувствовало. Будем иметь этот факт в виду.

Насчёт скриптовых языков - возьмём, к примеру, Ruby. Сейчас Ruby поддерживает запуск кода внутри файбера - для асинхронности - и обычный блокирующий режим. На уровне синтаксиса эти 2 режима никак не отличаются - в отличие от async/await в питоне. Как сказал создатель Ruby, Matz, Ruby сделал выбор в пользу совместимости кода. Есть реализация Ruby с JVM - JRuby, которая успешно внедрила проект Loom для реализации своих файберов. Есть также Truffle Ruby от GraalVM, который, вроде, в несколько раз производительнее, чем JRuby.

Так вот, моё мнение - что создатель Ruby очень даже правильный выбор сделал. Дело в том, что так уж сложилось, что Ruby используется в основном в веб-разработке. Async-only для н��го - вполне естественный и логичный выбор. Блокирующий режим просто может стать deprecated c какого-то момента: он не очень и нужен. Также, я считаю, очень вероятно, что версия с JVM станет основной. По той же самой причине - что Ruby используют для веб-разработки. Сервисы обмениваются данными по сети, и интеграция с нативным кодом не нужна. Изоляция от него, возникающая при использовании JVM - не недостаток.

Совсем другая история - с питоном, для него веб-разработка - не только не единственный, но и не главный сценарий использования - к счастью. Гвидо, добавив async/await, сделал выбор не в пользу совместимости кода, а строго наоборот. Только те библиотеки, которые не работают с вводом-выводом, могут быть использованы как в блокирующем, так и в асинхронном режиме - их немного. Что создаёт своего рода 2 непересекающихся мира внутри одного языка питон. По областям применения они - действительно не пересекающиеся: асинхронность нужна для веб-разработки, и её, наоборот, совершенно игнорируют в дата саенс, машинном обучении и околонаучных областях.

Альтернативные реализации питона (кроме CPython) существовали давно, например, PyPy, но никогда они популярными не были. Хотя все знают, что они могут дать прирост производительности в несколько раз. Проблема в том, что сишные расширения - неотъемлемая часть питона, и, в случае их наличия, часто они сами и есть бутылочное горлышко.

Реализация интерпретатора на JVM для питона тоже есть - от GraalVM. Но я вот что подумал: можно её использовать исключительно для асинхронного питона. Сишные экстеншены в таком случае не нужны - пусть расширяется с помощью JVM. Пусть всё запускается в виртуальных потоках. Благодаря выбору Гвидо в пользу async/await - это возможно! Это не сломает блокирующий режим.

Как это может выглядеть? Слова async/await можно просто игнорировать - они будут служить просто маркерами асинхронного кода (код и так будет выполняться в виртуальном потоке). Конечно же, вместо веб-сервера можно будет взять какой-нибудь ktor, а не gunicorn. Расширения нужно будет писать на Java/Kotlin - а не на Rust :)

Что касается Graal Python - реализации питона для JVM - то сейчас он двигается в другом направлении. Они стараются поддержать весь питон, причём, чтобы сишные экстеншены в нём не тормозили. Асинхронность там реализована на генераторах - то есть, как в CPython. А могли бы просто игнорировать слова async/await - всё выполнялось бы ощутимо быстрее.

В общем, надо подать им мою идею: лично я заниматься реализацией точно не собираюсь.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Что бы Вы выбрали, Project Loom или Kotlin Coroutines?
72%Project Loom18
28%Kotlin Coroutines7
Проголосовали 25 пользователей. Воздержались 12 пользователей.