Информация об изменениях

Сообщение Re[2]: Киллер фича JDK 21 - virtual threads от 02.06.2023 7:55

Изменено 02.06.2023 7:59 vdimas

Re[2]: Киллер фича JDK 21 - virtual threads
Здравствуйте, Sinclair, Вы писали:

S>Вкратце, как я понимаю, основная проблема зелёных потоков — взаимодействие с неуправляемым кодом.


В дотнете та же фигня, обсуждали уже при обсуждении ReaderWriterLockSlim.
Получилась ужасно неэффективная реализация ReaderWriterLockSlim, как раз ввиду того, что объект может дёргаться как обычным образом, так и через async-методы.

Не зря я сразу по выходу говорил, что async/await в дотнете не нужен, его добавили туда не от большого ума.
Ведь в дотнете сразу сделали правильно — отделили "поток" дотнета от потока ОС и сразу же объявили, что 1:1 соответствие не гарантируется.


S>Java пытается выехать на том, что весь код — управляемый, и все блокирующие IO операции заботливо подменены под капотом на вызовы неблокирующих с последующим yield.


При чём тут "управляемый"?
Это можно делать и в неуправляемом коде — как в первых версиях Windows до 3.х включительно, или еще кучи других тогдашних многозадачных сред, типа Novell NetWare.


S>Если мы имеем несчастье вызвать какую-то IO библиотеку, которая написана до JDK 21, то виртуальный поток вполне успешно встанет в ожидание, сожрав поток ОС.


Только если эта либа писана двоечником.

В Джаве отродясь рекомендовалось юзать в JNI-коде не левые мьютексы, а MonitorEnter/MonitorExit, которые в JNI даже эффективней выполняются:
https://gcc.gnu.org/onlinedocs/gcc-4.3.6/gcj/Synchronization.html


S>Но это — чисто performance issue, т.е. старый код продолжит работать так же, как он работал в старом JDK. Апгрейд на версию библиотеки, адаптированную к JDK 21, даст автоматическое улучшение масштабируемости приложения без переписывания кода.


Ничего не даст в плане IO, как ничего не дал async/await в сравнении с явной асинхронщиной на SocketAsyncEventArgs, даже хуже стало. ))
Просто эти async/await и соотв. новые методы понизили "планку входа", но не бесплатно — оно всё теперь работает заметно хуже, т.к. выше издержки.

Я ХЗ как ты упустил из виду то, что всем давно натёрло бельмо на глазу — есть фундаментальная проблема реализации зеленых потоков совместно с IO в современных ОС.

А именно — невозможно в одном АПИ ожидать готовность сокета и готовность мьютекса/семафора/события и т.д.

Например, под Windows это возможно частично — это надо привязать WSAEVENT к сокету, и такой HEVENT уже можно слушать готовность сокета через WaitForMultipleObjects вместе с семафорами и прочим, но там макс.кол-во хендлов 64, поэтому на этой основе "библиотеки широкого пользования" не пишут, из-зу нулевой масштабируемости.

Который IOCP — там можно слушать сокеты, но нельзя мьютексы.
Некоторые умельцы говорят, что слушают хендл IOCP через WaitForMultipleObjects, но фича недокументированная и ведёт себя по-разному в разных версиях Windows.

В Linux аналогично, полная задница ровно в этом же месте.
Есть костыли навроде eventfd, но они намного медленнее семафоров, плюс требуется порой переписывать почти всё в плане синхронизации, если до этого пользовались обычными примитивами синхронизации.

Поэтому, различные такие RTL языков с реализацией зеленых потоков или различные VM решают вопрос каждый в меру своей испорченности насчёт невозможности адекватного сосуществования пула потоков IO и пула потоков для зеленых потоков. Например, в дотнете это тупо два пула, и события часто передаются из пула в пул, что тормоза.

В других платформах, типа Go, Erlang — там ничего нельзя, кроме того, что дано изкаробки, что тоже не совсем адекватно в принятой в современном мире практики переиспользования уже имеющихся нейтивных библиотек на все случаи жизни. Как минимум где нужна малейшая синхронизация — их тянуть в Go и Erlang нельзя.

Собсно, поэтому эти языки никогда не выйдут из разряда "игрушечных", что в них тупо многое не работает.
Джава/дотнет в этом смысле позволяют разработчикам больше, но и цену платят дороже.
А, да, в Java NIO и NIO.2 всё по-уродски с IO-потоками, примерно как в дотнете.


S>Хуже — ситуация, когда мы вызываем какой-то неуправляемый код, который напрямую лезет в TLS средствами операционки.

S>Приняты ли в JDK 21 какие-то меры борьбы с таким поведением?

В TLS лезть можно, если знать куда, к синхронизации никаким боком.
Я тут долго медитировал, что ты хотел сказать, но решил, что это просто несвежие грибы. ))


S>Если нет — то приложение просто взорвётся при переходе на виртуальные потоки.


В нейтиве к TLS привязывают обычно данные именно потока ОС, а не "логического".
И используют для того, чтобы не заниматься синхронизацией доступа к данным и чтобы улучшать локальность, т.е. это обычно системные какие-нить вещи, типа пула объектов, аллокаторы, связанный с данным физическим потоком диспетчер легковесных потоков/задач/IO и т.д.

Иначе, во время обработки сигналов в Linux или в completion routines в Windows можно такого наобрабатывать, если прикладную логику на TLS закладывать.
Например, через completion routines можно обслужить сколько угодно сокетов в одном потоке в асинхронном режиме.
Отличие от IOCP в том, что IO-операция привязана к одному потоку, а не к пулу их.
Именно поэтому completion routines работают намного эффективней IOCP. ))
Но в этой технике хуже с балансировкой нагрузки, ес-но.


S>В остальном — конечно, было бы прикольно иметь возможность прозрачного использования кооперативной многозадачности без утомительного переписывания прямолинейного кода на код с async/await.


А ведь говорилось сразу, что хернёй маются с этими async-await, что при наличии низлежащих либ фреймворка, с полным покрытием IO, эти задачи решаются иначе.
Просто пошли сначала по лёгкому пути, дав 1-в-1 отображение АПИ ОС на либы дотнета...
А затем, вместо того, чтобы переписать некую часть фреймворка, добавили сущностей.
Породили монструозный уродский Task.
Потом еще более уродский ValueTask, где на каждый чих по 3 проверки типа объекта в бестиповом поле object _obj.
А теперь всё, поезд ушел окончательно, как теперь от этого всего отказаться? ))
Re[2]: Киллер фича JDK 21 - virtual threads
Здравствуйте, Sinclair, Вы писали:

S>Вкратце, как я понимаю, основная проблема зелёных потоков — взаимодействие с неуправляемым кодом.


В дотнете та же фигня, обсуждали уже при обсуждении ReaderWriterLockSlim.
Получилась ужасно неэффективная реализация ReaderWriterLockSlim, как раз ввиду того, что объект может дёргаться как обычным образом, так и через async-методы.

Не зря я сразу по выходу говорил, что async/await в дотнете не нужен, его добавили туда не от большого ума.
Ведь в дотнете сразу сделали правильно — отделили "поток" дотнета от потока ОС и сразу же объявили, что 1:1 соответствие не гарантируется.


S>Java пытается выехать на том, что весь код — управляемый, и все блокирующие IO операции заботливо подменены под капотом на вызовы неблокирующих с последующим yield.


При чём тут "управляемый"?
Это можно делать и в неуправляемом коде — как в первых версиях Windows до 3.х включительно, или еще кучи других тогдашних многозадачных сред, типа Novell NetWare.


S>Если мы имеем несчастье вызвать какую-то IO библиотеку, которая написана до JDK 21, то виртуальный поток вполне успешно встанет в ожидание, сожрав поток ОС.


Только если эта либа писана двоечником.

В Джаве отродясь рекомендовалось юзать в JNI-коде не левые мьютексы, а MonitorEnter/MonitorExit, которые в JNI даже эффективней выполняются:
https://gcc.gnu.org/onlinedocs/gcc-4.3.6/gcj/Synchronization.html


S>Но это — чисто performance issue, т.е. старый код продолжит работать так же, как он работал в старом JDK. Апгрейд на версию библиотеки, адаптированную к JDK 21, даст автоматическое улучшение масштабируемости приложения без переписывания кода.


Ничего не даст в плане IO, как ничего не дал async/await в сравнении с явной асинхронщиной на SocketAsyncEventArgs, даже хуже стало. ))
Просто эти async/await и соотв. новые методы понизили "планку входа", но не бесплатно — оно всё теперь работает заметно хуже, т.к. выше издержки.

Я ХЗ как ты упустил из виду то, что всем давно натёрло бельмо на глазу — есть фундаментальная проблема реализации зеленых потоков совместно с IO в современных ОС.

А именно — невозможно в одном АПИ ожидать готовность сокета и готовность мьютекса/семафора/события и т.д.

Например, под Windows это возможно частично — это надо привязать WSAEVENT к сокету, и такой HEVENT уже можно слушать готовность сокета через WaitForMultipleObjects вместе с семафорами и прочим, но там макс.кол-во хендлов 64, поэтому на этой основе "библиотеки широкого пользования" не пишут, из-за нулевой масштабируемости.
Плюс добавляется задержка на срабатывание HEVENT.

Который IOCP — там можно слушать сокеты, но нельзя мьютексы, семафоры.
Некоторые умельцы говорят, что слушают хендл IOCP через WaitForMultipleObjects, но фича недокументированная и ведёт себя по-разному в разных версиях Windows.

В Linux аналогично, полная задница ровно в этом же месте.
Есть костыли навроде eventfd, но они намного медленнее семафоров, плюс требуется порой переписывать почти всё в плане синхронизации, если до этого пользовались обычными примитивами синхронизации.

Поэтому, различные такие RTL языков с реализацией зеленых потоков или различные VM решают вопрос каждый в меру своей испорченности насчёт невозможности адекватного сосуществования пула потоков IO и пула потоков для зеленых потоков. Например, в дотнете это тупо два пула, и события часто передаются из пула в пул, что тормоза.

В других платформах, типа Go, Erlang — там ничего нельзя, кроме того, что дано изкаробки, что тоже не совсем адекватно в принятой в современном мире практики переиспользования уже имеющихся нейтивных библиотек на все случаи жизни. Как минимум где нужна малейшая синхронизация — их тянуть в Go и Erlang нельзя.

Собсно, поэтому эти языки никогда не выйдут из разряда "игрушечных", что в них тупо многое не работает.
Джава/дотнет в этом смысле позволяют разработчикам больше, но и цену платят дороже.
А, да, в Java NIO и NIO.2 всё по-уродски с IO-потоками, примерно как в дотнете.


S>Хуже — ситуация, когда мы вызываем какой-то неуправляемый код, который напрямую лезет в TLS средствами операционки.

S>Приняты ли в JDK 21 какие-то меры борьбы с таким поведением?

В TLS лезть можно, если знать куда, к синхронизации никаким боком.
Я тут долго медитировал, что ты хотел сказать, но решил, что это просто несвежие грибы. ))


S>Если нет — то приложение просто взорвётся при переходе на виртуальные потоки.


В нейтиве к TLS привязывают обычно данные именно потока ОС, а не "логического".
И используют для того, чтобы не заниматься синхронизацией доступа к данным и чтобы улучшать локальность, т.е. это обычно системные какие-нить вещи, типа пула объектов, аллокаторы, связанный с данным физическим потоком диспетчер легковесных потоков/задач/IO и т.д.

Иначе, во время обработки сигналов в Linux или в completion routines в Windows можно такого наобрабатывать, если прикладную логику на TLS закладывать.
Например, через completion routines можно обслужить сколько угодно сокетов в одном потоке в асинхронном режиме.
Отличие от IOCP в том, что IO-операция привязана к одному потоку, а не к пулу их.
Именно поэтому completion routines работают намного эффективней IOCP. ))
Но в этой технике хуже с балансировкой нагрузки, ес-но.


S>В остальном — конечно, было бы прикольно иметь возможность прозрачного использования кооперативной многозадачности без утомительного переписывания прямолинейного кода на код с async/await.


А ведь говорилось сразу, что хернёй маются с этими async-await, что при наличии низлежащих либ фреймворка, с полным покрытием IO, эти задачи решаются иначе.
Просто пошли сначала по лёгкому пути, дав 1-в-1 отображение АПИ ОС на либы дотнета...
А затем, вместо того, чтобы переписать некую часть фреймворка, добавили сущностей.
Породили монструозный уродский Task.
Потом еще более уродский ValueTask, где на каждый чих по 3 проверки типа объекта в бестиповом поле object _obj.
А теперь всё, поезд ушел окончательно, как теперь от этого всего отказаться? ))