Здравствуйте, ·, Вы писали:
·>Здравствуйте, Serginio1, Вы писали:
S>>Смысл я давно понял и единственный плюс виртуальных поток это сделать из синхронного кода асинхронный. ·>Ты совершенно неверно понял.
S>>
S>>Выводы по Java 21
·>Опять неспособность читать. Там ещё следующая глава в статье. Хинт: текущая версия это Java 25.
Что (вроде бы) остается опасным даже в 24–25
На границе 24–25 Java виртуальные потоки в почти идеальном состоянии, но важно помнить про «наследие» до Java 21:
Библиотеки, полагающиеся на ThreadLocal, например, старые реализации MDC в логировании, становятся узким местом при миллионах виртуальных потоков.
Вызовы через JNI по‑прежнему блокируют carrier thread.
Долгоживущие VT, например, бесконечные воркеры, = неоптимальное использование.
Когда виртуальные потоки — must have, а когда лучше не надо
Хорошие кейсы
Web-сервисы с пулами потоков и независимыми запросами, например, Tomcat, Jetty. После Java 25, где JEP 491 стабилен, можно смело включать — обработка тысяч параллельных запросов без издержек на thread pool.
HTTP-клиенты — практически «бесплатный» прирост производительности, можно запускать тысячи исходящих запросов без перегрузки потоков.
Параллельная обработка задач внутри бизнес-логики — каждый шаг в своем виртуальном потоке, без shared state.
Data-pipelines и ETL другая сложная ветвистая многопоточность — также можно смело переводить = упрощение структур многопоточности и дебага.
Плохие кейсы
Долгоживущие потоки/воркеры. Не используйте VT так же, как использовали старые: их не нужно пулить и держать в бесконечном цикле весь runtime.
Неоптимизированные библиотеки с ThreadLocal и большим числом блокирующих нативных вызовов — прироста не будет, скорее наоборот.
Вызовы через JNI могут навсегда занять carrier thread = потеря масштабируемости.
[q]
Простые советы, если вы уже перешли на свежую Java или только готовитесь
Мониторьте pinned-потоки. Несмотря на прогресс, проблемы с виртуальными потоками возможны, например, из-за JNI. Следите, чтобы не поймать кейсы «зависаний», как в нашем примере и у Netflix.
После того как Scoped Values стали stable, переходите на них. Избегайте бесконечных циклов и обновляйте библиотеки — это закрывает уязвимости, улучшает перформанс и адаптирует код под современные возможности языка.
Что можно писать сейчас и с какой версии Java
Делюсь своей табличкой‑шпаргалкой: тип задачи → применять ли виртуальные потоки → с какой версии Java → на что обратить внимание. Можете зафиксировать ее как напоминалку и использовать при планировании миграций.
[/q]
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, novitk, Вы писали:
N>Здравствуйте, Serginio1, Вы писали:
S>>Эти зеленые потоки уже обсуждали множество раз. Там единственный плюс, что все выплняется в одном потоке, поэтому не нужна синхронизация. N>зеленые потоки не выполняются в одном OS потоке и синхронизация нужна.
S>>То же можно сделать и на C# со своим шедулером с одним потоком. N>Бесшовно нельзя. Именно поэтому есть в C# Thread.Sleep и Таsk.Sleep, a в Го и jvm одна. Именно поэтому надо руками добавлять Task.Yield в числодробилки, а на Gо и jvm нет.
Здравствуйте, Serginio1, Вы писали:
S>Почитав S>Виртуальные потоки в Java: эволюция, практика, подводные камни S>Его суть использовать старый код в асинхронном виде.
Откуда такой бредовый вывод? Суть — сабж. Т.е. избавиться от необходимости асинхронного кода.
S>На C# c помощью ИИ легко сгенерировать новый асинхронных код заменив Thread.Sleep на асинхронный аналог Task.Delay, ThreadLocal на AsyncLocal итд
Наверное можно, и в тривиальных случаях может даже заработает как-то. Но зачем?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, mrTwister, Вы писали:
T>·>Ну т.е. создать новую функцию и передать всё через одно место (chan). И ещё каким-то хитровывернутым синтаксисом... T>А в чем проблема создать повторно используемую функцию на 3 строчки кода? Пошли придирки ради придирок. T>Какая тебе разница, что внутри chan, и чем он плох по-твоему? Ну замени chan на condition, сути это не поменяет, просто работать будет медленнее (chan в go очень сильно оптимизированы на уровне рантайма, например если какая-то функция читает канал, то писатель в канал запишет значение сразу в стек читателя, исключая тем самым лишнее копирование данных). Синтаксис стандартный в go.
А насколько оно всё гибко? Ну там, таймаут добавить, или просто завершения работы дождаться? Ошибку прокинуть? Отменить исполнение? Типичные шаблоны — wait-for-all, wait-for-any и т.п.?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
S>>Почитав S>>Виртуальные потоки в Java: эволюция, практика, подводные камни S>>Его суть использовать старый код в асинхронном виде. ·>Откуда такой бредовый вывод? Суть — сабж. Т.е. избавиться от необходимости асинхронного кода.
А нужно? В статье про подводные камни расписано. Проще использовать асинхронный код и использовать как параллельные асинхронные вычисления, так и последовательные, таймауты, CancellationToken итд итп.
S>>На C# c помощью ИИ легко сгенерировать новый асинхронных код заменив Thread.Sleep на асинхронный аналог Task.Delay, ThreadLocal на AsyncLocal итд ·>Наверное можно, и в тривиальных случаях может даже заработает как-то. Но зачем?
Почему в тривиальных. Все зависит от того, как поставишь задачу ИИ. Они очень умные
А зачем использовать виртуальные потоки? Ради производительности в многопользовательских серверах как правило.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Shmj, Вы писали:
N>>Ты, кажется, хотел сказать наоборот. nowait как раз вообще ничего не требует за пределами обычной парадигмы даже без async/await, это возврат к тому, что было до них.
S>Требует — это отказ от ожидания пока исполнится асинхронная операция.
Ну так это и есть та парадигма, что была до async/await в языках. Порождаем чем-то средозависимым асинхронную операцию и потом ждём её завершения (спим, поллим -- зависит от местности) или получаем сообщение/сигнал.
Разница в том, есть ли штатная поддержка варианта "мы готовы спать на любой точке ожидания, и не только", или нет.
Вон до какой-то версии горутина в вечном цикле не могла быть прервана снаружи. Уже совсем недавно добавили какой-то детект такой ситуации.
Здравствуйте, novitk, Вы писали:
SD>>Можно просто использовать язык, где эти костыли не нужны. Например, Erlang — там подобной дури нет и в помине. N>В Erlang разве не обычные синхронные функции, обертывание которых в процессы требует даже большей церемонии чем async/await в C#/JS/Python?
Именно так. Асинхронность появляется только там, где взаимодействие с другими процессами, причём в явном виде для кода и ещё и, предпочтительно, в чисто коллбэковом стиле. Аналог await в виде, например, gen_call или ряда операций со скрытым аналогом того же -- есть вызов чего-то от другого процесса с передачей сообщения и получением ответа. И при этом надо постараться не попасть в дедлок между такими обменами, никакого штатного детекта не будет.
Ну а активно прорекламировать с вывертом мозга -- то, от чего коллега SkyDance не может удержаться, даже при том, что он ушёл от работы, где его используют ;(
Здравствуйте, mrTwister, Вы писали:
T>>>Я же написал: запусти его с GOMAXPROCS=1
S>>И что должно быть?
T>Всегда будет один ThreadID, при том, что запущенные функции работают одновременно. Это возможно только если выполняемые в горутинах функции асинхронны.
Они полностью синхронны в стандартном определении -- так, как синхронными являются несколько процессов в Unix, Windows, где угодно, которые, например, читают из одного блокирующего (по умолчанию) сокета и пишут в другой, и при этом работают "одновременно" на одноядерной машине.
А то, что при этом в ядре работает кооперативная многозадачность, и что Unix, что Windows, делает, что уже в ядре вызов типа recv(), если нет данных в буфере, переходит в sleep() и ожидает, пока кто-то, кто пишет данные в буфер сокета (как хэндлер сетевухи) не сделает парный ему wakeup() -- это именно для пользовательского кода не считается асинхронностью.
Go реализует, по сути, то же самое, но в юзерленде переключая горутины между своими нитками ОС. Но это видно в потрохах рантайма, а не автору кода на Go. Для автора кода это всё детали исполнения, а те функции, которые он сам может вызвать -- кроме select или ожидания "один из группы" -- синхронны.
Может, кто-то сочтёт это вопросом терминологии, но не принято называть ни чтение из блокирующего сокета, ни какой-нибудь ReadFile() не overlapped -- асинхронной операцией. Асинхронная -- это при заказе операции генерируется объект типа promise, future или как там будет в конкретном языке/библиотеке, и потом с этим promise можно что-то делать (проверять готовность, спать на результате, посылать отмену). Если же результат сразу, то это синхронная.
Здравствуйте, Shmj, Вы писали:
T>>Все это есть в Go, но без явных языковых конструкций. T>>Когда ты в Go пишешь "sum(1, 2)" тут неявно подразумевается псевдокод "await sum(1,2)". У тебя вызывающая функция до sum может выполняться в одном потоке ОС, а после "x := sum(1, 2)" может продолжить выполняться вообще в другом потоке (то есть так же, как в C# варианте).
S>Почему ты так думаешь? Попробуй доказать это в коде.
S>Должна быть возможность получить что-то типа Task или Promise или Future не дожидаясь исполнения и добавить эти промисы в список, к примеру. Потом подождать пока исполнится весь список и получить результат каждого из списка.
S>В Go оператор go не может вернуть промис или что-то подобное, а значит совсем другая концепция.
Ну оно (относительно) легко эмулируется:
1) Создать канал для конкретной горутины.
2) Породить горутину, которая пишет в этот канал.
3) Уже каналы можно ожидать по одному, первому ответившему (штатный select) или всех вместе (функция для этого легко пишется).
Код рисовать не буду, я не опытный писатель на нём. Но опытному должно быть очевидно.
Здравствуйте, Shmj, Вы писали:
S>Допустим, по умолчанию все функции сделать async, а компилятор уже сам оптимизирует.
S>Понятно что для системных языков это не годится, но для бизнес-языков вполне.
S>Получается если не нужно ждать результата функции — пишем наоборот — nowait. Если ждать результат — ничего не пишем, по умолчанию.
S>Т.к. в основном асинхронных больше и даже если какая не асинхронная — то компилятор мог бы сам оптимизировать.
Я бы задумался над DSL аля TypeScript, который из asinc/await генерил классы для асинхронного выполнения, когда JavaScript не было asinc/await.
Используя ИИ можно создать свой анализатор на Roslyn который будет преобразовывать синхронный код в асинхронный.
добавить асинхронные методы с суфиксом Async и сгенерировать новый асинхронных код заменив Thread.Sleep на асинхронный аналог Task.Delay, ThreadLocal на AsyncLocal итд
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>·>Откуда такой бредовый вывод? Суть — сабж. Т.е. избавиться от необходимости асинхронного кода. S>А нужно? В статье про подводные камни расписано.
Какие конкретно камни ты имеешь в виду и как они обходятся в c#?
S> Проще использовать асинхронный код и использовать как параллельные асинхронные вычисления, так и последовательные, таймауты итд итп.
Чем проще? Ты сабж видел? Тут уже кучу раз растолковали недостатки сабж. Ты даже сам цитировал.
S>·>Наверное можно, и в тривиальных случаях может даже заработает как-то. Но зачем? S> Почему в тривиальных. Все зависит от того, как поставишь задачу ИИ. Они очень умные
Так ради чего создавать лишнюю сложность в виде копипасты Thread.Sleep/Task.Delay и т.п., чтобы потом пришлось отважно бороться ИИями? В чём ценность сего действа?
А просто решать сами бизнес-задачи (хоть с ИИ, хоть без) — не солидно что-ли?
S>А зачем использовать виртуальные потоки? Ради производительности в многопользовательских серверах как правило.
Затем, что код с потоками проще, даже если понадобится высокая производительность и много пользователей.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, netch80, Вы писали:
N>Ну оно (относительно) легко эмулируется: N>1) Создать канал для конкретной горутины. N>2) Породить горутину, которая пишет в этот канал. N>3) Уже каналы можно ожидать по одному, первому ответившему (штатный select) или всех вместе (функция для этого легко пишется).
Приколхозить можно что угодно, но стандартизированного удобного механизма нет.
·>Так ради чего создавать лишнюю сложность в виде копипасты Thread.Sleep/Task.Delay и т.п., чтобы потом пришлось отважно бороться ИИями? В чём ценность сего действа? ·>А просто решать сами бизнес-задачи (хоть с ИИ, хоть без) — не солидно что-ли?
В том, что бы перенести старый код. Только и всего.
S>>А зачем использовать виртуальные потоки? Ради производительности в многопользовательских серверах как правило. ·>Затем, что код с потоками проще, даже если понадобится высокая производительность и много пользователей.
Так и с тасками он не особо то и сложный. Учитывая разного рода Task.WhenAll, Task.WhenAny, CancellationToken
Здравствуйте, Serginio1, Вы писали:
S>·>Какие конкретно камни ты имеешь в виду и как они обходятся в c#?
ИЧСХ, ответа не последовало.
S>·>Так ради чего создавать лишнюю сложность в виде копипасты Thread.Sleep/Task.Delay и т.п., чтобы потом пришлось отважно бороться ИИями? В чём ценность сего действа? S>·>А просто решать сами бизнес-задачи (хоть с ИИ, хоть без) — не солидно что-ли? S>В том, что бы перенести старый код. Только и всего.
Т.е. самоцель. Ценность переписывания кода — перенести старый код. Круто, чё. Хобби у тебя, видимо, такое: переписывание кода из пустого в порожнее.
S>·>Затем, что код с потоками проще, даже если понадобится высокая производительность и много пользователей. S>Так и с тасками он не особо то и сложный. Учитывая разного рода Task.WhenAll, Task.WhenAny, CancellationToken
Ок, т.е. ты соглашаешься, что код с потоками проще. Надеюсь, ты получил ответ на свой вопрос.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
S>>·>Какие конкретно камни ты имеешь в виду и как они обходятся в c#? ·>ИЧСХ, ответа не последовало.
Я тебе уже приводил статью, там все расписано.
S>>·>Так ради чего создавать лишнюю сложность в виде копипасты Thread.Sleep/Task.Delay и т.п., чтобы потом пришлось отважно бороться ИИями? В чём ценность сего действа? S>>·>А просто решать сами бизнес-задачи (хоть с ИИ, хоть без) — не солидно что-ли? S>>В том, что бы перенести старый код. Только и всего. ·>Т.е. самоцель. Ценность переписывания кода — перенести старый код. Круто, чё. Хобби у тебя, видимо, такое: переписывание кода из пустого в порожнее.
Не самоцель, а необходимость переноса старого кода, для увеличения производительности серверов.
S>>·>Затем, что код с потоками проще, даже если понадобится высокая производительность и много пользователей. S>>Так и с тасками он не особо то и сложный. Учитывая разного рода Task.WhenAll, Task.WhenAny, CancellationToken ·>Ок, т.е. ты соглашаешься, что код с потоками проще. Надеюсь, ты получил ответ на свой вопрос.
Я соглашаюсь, что он проще, но и примитивнее. Бейсик тоже простой, только вот много на нем не сделаешь.
Многие тоже по началу воротили нос от задач с async/await
С Задачами у тебя намного больше свободы. Кроме того есть прекрасная вещь как TaskCompletionSource c которым ты сам можешь контролировать задачу.
Пример
Здравствуйте, Serginio1, Вы писали:
S>>>·>Какие конкретно камни ты имеешь в виду и как они обходятся в c#? S>·>ИЧСХ, ответа не последовало. S> Я тебе уже приводил статью, там все расписано.
Т.е. статью ты не осилил, на простой вопрос ответить не можешь. ЧТД.
S>>>В том, что бы перенести старый код. Только и всего. S>·>Т.е. самоцель. Ценность переписывания кода — перенести старый код. Круто, чё. Хобби у тебя, видимо, такое: переписывание кода из пустого в порожнее. S> Не самоцель, а необходимость переноса старого кода, для увеличения производительности серверов.
Откуда эта необходимость возникла, и как от неё можно избавиться?
S>·>Ок, т.е. ты соглашаешься, что код с потоками проще. Надеюсь, ты получил ответ на свой вопрос. S> Я соглашаюсь, что он проще, но и примитивнее. Бейсик тоже простой, только вот много на нем не сделаешь.
Что конкретно у тебя не получается?
S>Многие тоже по началу воротили нос от задач с async/await
И сейчас воротят. См. сабж.
S>С Задачами у тебя намного больше свободы. Кроме того есть прекрасная вещь как TaskCompletionSource c которым ты сам можешь контролировать задачу.
Аналог CompletableFuture, похоже.
S>Пример S>AsyncProducerConsumerCollection
S>public class AsyncProducerConsumerCollection<T>
Опасная структура. Она должна быть bounded size для создания back pressure, иначе всё навернётся в самый неподходящий момент. Вот только добавить maxSize ты уже вряд ли осилишь.
S>Используя эту структуру данных, можно написать код, например следующий:
Тут ведь как... Можно написать, но можно и не писать! Меньше кода — лучше.
S>Кроме того и от блокировок можно избавляться
Так ведь можно и не избавляться!
S>
S>// БЫЛО
S>// СТАЛО
S>
Было мало кода, был простой код. Стало много сложного кода. Что ты хотел мне этим продемострировать?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Было мало кода, был простой код. Стало много сложного кода. Что ты хотел мне этим продемострировать?
Угу был дермовый код с блокировками потоков и хранением переменных привязанных к потоку.
Стал неблокирующий потоки, при этом код внутри монитора может быть асинхронный и переменные привязанные к задаче.
Проще не значит лучше!
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>·>Было мало кода, был простой код. Стало много сложного кода. Что ты хотел мне этим продемострировать? S>Угу был дермовый код с блокировками потоков и хранением переменных привязанных к потоку.
Что в этом плохого?
S>Стал неблокирующий потоки, при этом код внутри монитора может быть асинхронный и переменные привязанные к задаче.
Чем это лучше?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
S>>·>Было мало кода, был простой код. Стало много сложного кода. Что ты хотел мне этим продемострировать? S>>Угу был дермовый код с блокировками потоков и хранением переменных привязанных к потоку. ·>Что в этом плохого?
Ты серьезно? Проблема серверов в переключении потоков, на это тратится время, плюс память на стек для потока.
При использовании задач используется пул потоков, а задача представляет собой замыкание с автоматом переходов после выполнения задачи.
По сути это энумератор с MoveNext
S>>Стал неблокирующий потоки, при этом код внутри монитора может быть асинхронный и переменные привязанные к задаче. ·>Чем это лучше?
Ну во первых lock используется только внутри одного потока, при использовании асинхронного подхода нужно использовать SemaphoreSlim.
То есть количество кода будет тем же или больше.
Остальное смотри выше.
и солнце б утром не вставало, когда бы не было меня