S>>Эти зеленые потоки уже обсуждали множество раз. Там единственный плюс, что все выплняется в одном потоке, поэтому не нужна синхронизация. N>зеленые потоки не выполняются в одном OS потоке и синхронизация нужна.
Виртуальные потоки — это упрощённая реализация потоков, предоставляемая JDK, а не операционной системой. Они представляют собой разновидность потоков пользовательского режима, которые успешно используются в других многопоточных языках (например, горутины в Go и процессы в Erlang). Потоки пользовательского режима даже назывались «зелёными потоками» в ранних версиях Java, когда потоки операционной системы ещё не были широко распространены. Однако все «зелёные» потоки Java использовали один поток ОС (планирование M:1) и в конечном счёте уступали в производительности платформенным потокам, реализованным как оболочки для потоков ОС (планирование 1:1). Виртуальные потоки используют планирование M:N, при котором большое количество (M) виртуальных потоков планируется к запуску на меньшем количестве (N) потоков ОС.
S>>То же можно сделать и на C# со своим шедулером с одним потоком. N>Бесшовно нельзя. Именно поэтому есть в C# Thread.Sleep и Таsk.Sleep, a в Го и jvm одна. Именно поэтому надо руками добавлять Task.Yield в числодробилки, а на Gо и jvm нет.
Task.Delay. В асинхронном коде Thread.Sleep не используют. Task.Yield это что бы разбить на части. Кстати в задачах используются и CancellationToken , что бы прекратить задачу.
S>>
S>>В современных версиях Java, таких как Java 8 и выше, зелёные потоки не используются по умолчанию, и управление потоками передаётся операционной системе.
Про потоки в Java важно знать, что они — нормальные потоки операционной системы. Когда-то в первых JVM были реализованы так называемые green threads — зелёные потоки, когда на самом деле стек java-потока как-то жил своей жизнью, и один поток операционной системы выполнял то один java-поток, то другой. Это всё развивалось до тех пор, пока в операционных системах не появилась нормальная многопоточность. После этого все забыли «зелёные» потоки как страшный сон, потому что с нативными потоками код работает лучше.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
N>>Бесшовно нельзя. Именно поэтому есть в C# Thread.Sleep и Таsk.Sleep, a в Го и jvm одна. Именно поэтому надо руками добавлять Task.Yield в числодробилки, а на Gо и jvm нет. S> Task.Delay. В асинхронном коде Thread.Sleep не используют. Task.Yield это что бы разбить на части. Кстати в задачах используются и CancellationToken , что бы прекратить задачу.
Да, похоже у тебя очень сложный диагноз.
Здравствуйте, novitk, Вы писали:
N>Будь они обычные, как в Project Loom, тебе бы RunTask, aka Task.Run, не понадобился. Там семантика результата функций поддерживается в обеих контекстах, а в GoLang только в синхронном.
И какие проблемы с RunTask, который хелпер на несколько строк кода? Это такая же великая проблема языка go, как и отсутствие функции max в стандартной библиотеке (в последних версиях go правда добавили)
SD>Ага. SD>Собственно, Erlang тем и отличается, что у них process не просто структура данных, а еще и всякие поля для scheduling'а, и структура эта определена в виртуальной машине, а не просто как часть user-space library.
Здравствуйте, ·, Вы писали:
T>>Делается тривиальный хелпер на три строчки и код будет выглядеть точно так же ·>Ну т.е. создать новую функцию и передать всё через одно место (chan). И ещё каким-то хитровывернутым синтаксисом...
А в чем проблема создать повторно используемую функцию на 3 строчки кода? Пошли придирки ради придирок.
Какая тебе разница, что внутри chan, и чем он плох по-твоему? Ну замени chan на condition, сути это не поменяет, просто работать будет медленнее (chan в go очень сильно оптимизированы на уровне рантайма, например если какая-то функция читает канал, то писатель в канал запишет значение сразу в стек читателя, исключая тем самым лишнее копирование данных). Синтаксис стандартный в go.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, gandjustas, Вы писали:
S>>>Получается если не нужно ждать результата функции — пишем наоборот — nowait. Если ждать результат — ничего не пишем, по умолчанию. G>>Так работает Go
S>Проверял или просто веришь?
Не проверял, но по косвенным признакам это так. Подробности ниже.
S>Вот C#
S>
S>async Task<int> SumAsync(int a, int b) {
S> return a + b;
S>}
S>var x = await SumAsync(2, 3);
S>
Если что в этом коде func гарантированно выполнится в том же потоке, вернется промис aka Task, и await в вызывающем коде сразу же получит ризультат. Никакого асинхронного выполнения не будет.
S>Эквивалент в Go
S>
S>func sumAsync(a, b int) <-chan int {
S> ch := make(chan int, 1)
S> go func() {
S> ch <- a + b
S> close(ch)
S> }()
S> return ch
S>}
S>x := <-sumAsync(2, 3)
S>
В этом коде ты явно создаешь новый поток, в котором выполняется функция сложения, которая в канал пересылает результат. Что дает асинхронное выполнение функции, но потом ты в вызывающем коде синхронно ожидаешь реультата асинхронной функции.
Эквивалент на C# будет гораздо сложнее, там и Task.Run, и System.Threading.Channels.
Эквивалент в Go твоего примера на C# будет такой:
func sum(a, b int) int {
return a+b;
}
x:=sum(2,3)
Функция также выполнится синхронно, также вызывающий код получит результат.
Самое интересное происходит когда внутри функции у тебя есть IO или другая операция, которая может выполниться в неблокирующем режиме
async Task<?> read() {
var data = await System.IO.File.ReadAllBytesAsync("image.png")
...
}
x = await read();
Почему работает ровно то, что ты хотел получить — когда нужно ждать результата, то не пишем ничего. А если нужно не ждать, то вызываем функцию через оператор go.
Вызываются ли IO функции асинхронно я не проверял.
Но если запустить 1000 горутин и во всех них вызвать IO функции, то они будут выполняться параллельно, хотя потоков будет гораздо меньше. Это поведение я неоднократно наблюдал.
Здравствуйте, gandjustas, Вы писали:
G>Эквивалент в Go твоего примера на C# будет такой:
func sum(a, b int) int {
return a+b;
}
x:=sum(2,3)
Не будет и близко. В C# я могу получить Task (в JS — Promise, в Dart — Future и т.д). Это позволяет мне писать легко и просто вот такое:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static int _globalCounter = 0;
static async Task Main()
{
const int n = 10;
var tasks = new List<Task<int>>(n);
for (int i = 0; i < n; i++)
{
tasks.Add(Task.Run(async () =>
{
await Task.Delay(1000);
int current = Interlocked.Increment(ref _globalCounter);
if (current % 2 == 0)
throw new InvalidOperationException($"Ошибка: счетчик={current}, нельзя вернуть чётное значение.");
return current;
}));
}
// Ждём все задачи и "гасим" исключение на уровне await через continuation
await Task.WhenAll(tasks).ContinueWith(t =>
{
// можно оставить пусто
// если нужно — тут можно залогировать:
// if (t.Exception != null) Console.WriteLine(t.Exception);
}, TaskContinuationOptions.ExecuteSynchronously);
var successful = tasks
.Where(t => t.Status == TaskStatus.RanToCompletion)
.Select(t => t.Result)
.ToList();
var errors = tasks
.Where(t => t.IsFaulted)
.SelectMany(t => t.Exception!.Flatten().InnerExceptions)
.Select(ex => ex.Message)
.ToList();
Console.WriteLine("Успешные результаты: " + (successful.Count == 0 ? "(нет)" : string.Join(", ", successful)));
Console.WriteLine("Сумма успешных результатов: " + successful.Sum());
Console.WriteLine($"Успешно: {successful.Count}, с ошибкой: {errors.Count}");
Console.WriteLine("Тексты ошибок:");
if (errors.Count == 0)
{
Console.WriteLine("(нет)");
}
else
{
foreach (var e in errors)
Console.WriteLine(" - " + e);
}
Console.WriteLine("Финальный глобальный счетчик: " + _globalCounter);
}
}
T>Тем, что func1 и func2 обязаны быть async на всю глубину вызовов и не делать блокирующих io операций
В его примере func1 и func2 это обычные методы. И я привел
Что касается asнnc методов то на C# используются асинхронные функции. Зачем использовать синхронные в асинхронном методе?
и солнце б утром не вставало, когда бы не было меня
Так горутины с самого начала были прямой копией "процессов". Разница заключалась только в подходе к GC, в случае с Erlang одним из требований было как раз полное отсутствие stop-the-world фазы у сборщика. Поэтому GC в эрланге осуществляется для каждого процесса отдельно (как если бы каждая горутина имела свою heap, а не только стек). Что потянуло за собой shared-nothing модель (разумеется, с оговорками, — например, binaries размером больше 64 (или как-то так, я уже точно не помню) уже являются ref-counted), ну а дальше оказалось, что immutable variables даже более элегантно смотрятся в языке.
Здравствуйте, Serginio1, Вы писали:
T>>Тем, что func1 и func2 обязаны быть async на всю глубину вызовов и не делать блокирующих io операций S>В его примере func1 и func2 это обычные методы. И я привел
S>Что касается asнnc методов то на C# используются асинхронные функции. Зачем использовать синхронные в асинхронном методе?
Перечитай что тут писали. Твой ответ просто невпопад.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
T>>>Тем, что func1 и func2 обязаны быть async на всю глубину вызовов и не делать блокирующих io операций S>>В его примере func1 и func2 это обычные методы. И я привел
S>>Что касается asнnc методов то на C# используются асинхронные функции. Зачем использовать синхронные в асинхронном методе?
·>Перечитай что тут писали. Твой ответ просто невпопад.
Читал. Основное "преимущество" типа заключается в том, что не нужно блокировать общие ресурсы ибо зеленый поток реально работает в одном нативном потоке.
Ну и единственно, что явовский компилятор может их синхронного IO сделать его асинхронным.
Но суть в том, что при использовании пула нативных потоков производительность системы даже с блокировками значительно выше зеленых потоков.
Поэтому Java и отказывается от зеленых потоков в пользу нативных.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>Поэтому Java и отказывается от зеленых потоков в пользу нативных.
Перечитай ещё раз. В java нет зелёных потоков четверть века.
Речь шла о виртуальных потоках и project loom.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
S>>Поэтому Java и отказывается от зеленых потоков в пользу нативных. ·>Перечитай ещё раз. В java нет зелёных потоков четверть века. ·>Речь шла о виртуальных потоках и project loom.
Здравствуйте, ·, Вы писали:
·>Здравствуйте, Serginio1, Вы писали:
S>>·>Речь шла о виртуальных потоках и project loom. S>> Угу яве то чуть больше четверть века. ·>Очередной ответ невпопад.
S>>Виртуальные потоки в Java: эволюция, практика, подводные камни S>>
S>>go f() — и готово.
·>Очередная демонстрация неспособности читать. Эта цитата о GoLang.
Согласен
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("Привет из корутины!")
}
println("Привет из main")
delay(1000L)
}
Нужно уметь работать с context«ами, scope, Job, Dispatcher.
Возможны утечки при неправильном использовании.
Что обещали и что получилось
Ожидания: миллионы легких потоков, «магическое» устранение простоев на I/O, совместимость со старым кодом, простота reasoning, минимум усилий со стороны разработчика.
Реальность в целом подтвердилась — если не задевать проблемные места. Там, где нет synchronized, нет повсюду ThreadLocal и не нужно дебажить потоки, все работает, как задумано. Но в продакшене быстро проявились нюансы.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>Смысл я давно понял и единственный плюс виртуальных поток это сделать из синхронного кода асинхронный.
Ты совершенно неверно понял.
S>
S>Выводы по Java 21
Опять неспособность читать. Там ещё следующая глава в статье. Хинт: текущая версия это Java 25.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай