Здравствуйте, ·, Вы писали:
T>>Мьютекс сам по себе не позволит тебе конкурентно выполнять несколько "просессов" на одном потоке. Тебе нужно разбивать "процессы" на подзадачи, строить цепочки таскок и переключаться между ними. ·>Это понятно что модели как пишется код, конечно, разные. Но корректно продуманная кооперация требуется ровно так же.
Ну в этом смысла да, примитивы синхронизации — это механизм кооперации, с помощью которого потоки могут друг с другом о чем-то договориться.
S>[cut] S>[q] S>Если бы в Go все функции были асинхронными, в языке существовали бы:
S>
S> implicit future, S> await-поведение, S> выражение результата, S> continuation semantics. S>
S>Ничего этого в Go нет.
Все это есть в Go, но без явных языковых конструкций.
Когда ты в Go пишешь "sum(1, 2)" тут неявно подразумевается псевдокод "await sum(1,2)". У тебя вызывающая функция до sum может выполняться в одном потоке ОС, а после "x := sum(1, 2)" может продолжить выполняться вообще в другом потоке (то есть так же, как в C# варианте).
По умолчанию вызов функции в Go — синхронный: выполняется в текущей горутине и возвращает управление только после return
Дальше не читал. Горутина в go — это просто структура данных. Как, например, Task в .net. Не в смысле, что это аналог, в смысле что это вспомогательная структура данных для организации вычислений. Вызов функции в go блокирует горутину точно так же, как await блокирует асинхронную функцию в C#
Давай так, объясни, чем в плане поведения (а так же потребление ресурсов, потоков) отличаются C# вариант от go варианта?
async Task<int> SumAsync(int a, int b) {
return a + b;
}
var x = await SumAsync(2, 3);
func sum(a, b int) int {
return a + b
}
x := sum(1, 2)
подсказка: вообще ничем не отличаются, это полные аналоги
Здравствуйте, mrTwister, Вы писали:
T>Все это есть в Go, но без явных языковых конструкций. T>Когда ты в Go пишешь "sum(1, 2)" тут неявно подразумевается псевдокод "await sum(1,2)". У тебя вызывающая функция до sum может выполняться в одном потоке ОС, а после "x := sum(1, 2)" может продолжить выполняться вообще в другом потоке (то есть так же, как в C# варианте).
Почему ты так думаешь? Попробуй доказать это в коде.
Должна быть возможность получить что-то типа Task или Promise или Future не дожидаясь исполнения и добавить эти промисы в список, к примеру. Потом подождать пока исполнится весь список и получить результат каждого из списка.
В Go оператор go не может вернуть промис или что-то подобное, а значит совсем другая концепция.
Здравствуйте, mrTwister, Вы писали:
T>Дальше не читал. Горутина в go — это просто структура данных. Как, например, Task в .net. Не в смысле, что это аналог, в смысле что это вспомогательная структура данных для организации вычислений. Вызов функции в go блокирует горутину точно так же, как await блокирует асинхронную функцию в C#
T>Давай так, объясни, чем в плане поведения (а так же потребление ресурсов, потоков) отличаются C# вариант от go варианта?
Вся суть async/await — в том что можно получить Task/Promise/Future и манипулировать ими. Т.е. можете дождаться результата — а можете не дожидаться — но не просто не дожидаться — а заиметь промис, в котором будет результат.
Без возможности получить промис — все теряет смысл.
Здравствуйте, Shmj, Вы писали:
вообще в другом потоке (то есть так же, как в C# варианте).
S>Почему ты так думаешь? Попробуй доказать это в коде.
S>Должна быть возможность получить что-то типа Task или Promise или Future не дожидаясь исполнения и добавить эти промисы в список, к примеру. Потом подождать пока исполнится весь список и получить результат каждого из списка.
Здравствуйте, Shmj, Вы писали:
S>> А вызов без await подсвечивается в редакторе.
S>Так это не ошибка — вдруг вы так и задумали — пусть себе исполняется а вы будете другое делать.
Ну это предупреждение, которое можно подавить различными способами.
Предупреждён — значит вооружён!
и солнце б утром не вставало, когда бы не было меня
S>Вся суть async/await — в том что можно получить Task/Promise/Future и манипулировать ими. Т.е. можете дождаться результата — а можете не дожидаться — но не просто не дожидаться — а заиметь промис, в котором будет результат.
S>Без возможности получить промис — все теряет смысл.
Все это есть в go:
// Вот моя асинхронная функция
func sum(a, b int) int {
return a + b
}
// Я могу вызвать ее и дождаться результата (поток ОС при этом не блокируется)
x := sum(1, 2)
// Я могу вызвать и не дожидаться результата (при этом несмотря на создание новой горутины, это может все работать на одном единственном потоке ОС)
go sum(1, 2)
// В крайне редких кейзах, когда мне не нужен результат прям сейчас, а нужен когда-то потом, то я могу написать
result := make(chan int)
go func(){result <- sum(1,2)}()
... куча кода
println(<-result)
Во всех вариантах это асинхронный код, который не блокирует потоки ОС и конкурентно таких функций может быть запущено сотни тысяч
Здравствуйте, mrTwister, Вы писали:
T>Это все есть, но под капотом внутри рантайма go.
Блажен кто верует. Вот код, зачем спорить. C#
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Program
{
// Глобальный счетчик (общий ресурс для всех потоков)private static int _globalCounter = 0;
public static async Task Main()
{
// Список для хранения запущенных задач (аналог Future/Promise)var tasks = new List<Task<int>>();
int numberOfTasks = 10;
Console.WriteLine("--- 1. Запуск задач ---");
for (int i = 0; i < numberOfTasks; i++)
{
// Task.Run ставит задачу в очередь ThreadPool.
// Мы НЕ используем await здесь, чтобы не блокировать цикл.
// Мы просто сохраняем "хэндл" задачи в список.
Task<int> task = Task.Run(async () =>
{
// Имитация работы (задержка 100мс), чтобы потоки точно работали параллельно
await Task.Delay(100);
// Атомарное увеличение счетчика.
// Это аналог counter++ только потокобезопасный.
// Функция возвращает уже увеличенное значение.return Interlocked.Increment(ref _globalCounter);
});
tasks.Add(task);
}
Console.WriteLine($"Запущено {tasks.Count} задач. Главный поток свободен и продолжает работу...");
// Здесь можно делать что-то еще, пока таски крутятся в фоне
Console.WriteLine("--- 2. Ожидание выполнения ---");
// Task.WhenAll эффективно ждет завершения всех задач из списка.
// Он возвращает массив результатов, когда последний таск закончит работу.int[] results = await Task.WhenAll(tasks);
Console.WriteLine("Все задачи завершены!");
Console.WriteLine("--- 3. Вывод результатов ---");
// Проходимся по полученным результатам
// Порядок в массиве results соответствует порядку задач в списке tasksfor (int i = 0; i < results.Length; i++)
{
Console.WriteLine($"Таск #{i + 1} вернул значение счетчика: {results[i]}");
}
Console.WriteLine($"Итоговое значение глобального счетчика: {_globalCounter}");
}
}
Попробуй повторить на Go. Не сможешь, т.к. нет там асинхронных механизмов, есть только небольшие оптимизации, которые позволяют параллелить — но это везде так по умолчанию на уровне процессора.
T>// Я могу вызвать и не дожидаться результата (при этом несмотря на создание новой горутины, это может все работать на одном единственном потоке ОС)
T>
Та епта, так можно и в C# 2.0 вызывать в потоке. А ты попробуй промис получить. Вся суть в возможности получить промис — а просто запустить в отдельном потоке — никакой ценности не представляет.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, mrTwister, Вы писали:
T>>
T>>// Я могу вызвать и не дожидаться результата (при этом несмотря на создание новой горутины, это может все работать на одном единственном потоке ОС)
T>>
S>Та епта, так можно и в C# 2.0 вызывать в потоке. А ты попробуй промис получить. Вся суть в возможности получить промис — а просто запустить в отдельном потоке — никакой ценности не представляет.
Дак я не в потоке вызывал. Выделил главное. Ключевое слово "go" не в потоке запускает
Здравствуйте, Shmj, Вы писали:
S>Вот перевод на Go — посмотри насколько криво: https://go.dev/play/p/oDoPGmB7QU1
S>Есть чем возразить? Очевидно что нет.
Тут два момента: кривость и асинхронность, предлагаю обсудить их отдельно.
Асинхронность
Оба варианта асинхронные и несмотря на запуск 10 тасок/10 горутин, будет по факту использовано скорее не 10 потоков, а меньше. Таким образом оба варианта можно, например, безопасно вызывать в http хендлере не опасаясь заблокировать работу http сервера из-за thread pool depletion
Кривость
Код по сути идентичный с поправкой на то, что в go используются чуть более низкоуровневые примитивы. Но никто не мешает написать пару хелперов на несколько строк кода и получить абсолютно идентичное решение: https://go.dev/play/p/vV97PJoKyIB
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, mrTwister, Вы писали:
T>>Дак я не в потоке вызывал. Выделил главное. Ключевое слово "go" не в потоке запускает
S>В потоке (для каждого go создается свой поток), вот доказательство: https://go.dev/play/p/oDoPGmB7QU1
Блин, не не спорь пожалуйста, ты же понятия не имеешь, как работает go, но споришь
S>Если бы не в потоке, то вывод был бы последовательный — а так какая успела — та первой счетчик и изменила.
Нет конечно, time.Sleep создает под капотом таймер, который при срабатывании вызывает continuation у горутины. В зависимости от того, в каком порядке сработали эти таймеры, получится разный порядок цифр.
Go выполняет горутины в тредпуле, но размер этого тредпула фиксирован и определяется при старте программы (по умолчанию равен количеству ядер процессора). При этом ты можешь запустить свою программу с переменной окружения GOMAXPROCS=1, в этом случае для выполнения всех горутин будет использоваться один единственный поток ОС, что не помешает всем горутинам выполняться "одновременно", то есть конкурентно.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, mrTwister, Вы писали:
T>>Дак я не в потоке вызывал. Выделил главное. Ключевое слово "go" не в потоке запускает
S>В потоке (для каждого go создается свой поток), вот доказательство: https://go.dev/play/p/oDoPGmB7QU1
Здравствуйте, mrTwister, Вы писали:
T>Асинхронность T>Оба варианта асинхронные
Как ты это сможешь доказать? Ок, c go — асинхронный, верю. А без go — какие ваши доказательства что оно асинхрон?
T>и несмотря на запуск 10 тасок/10 горутин, будет по факту использовано скорее не 10 потоков, а меньше.
Так это и в C# — там ThreadPool.
T>Таким образом оба варианта можно, например, безопасно вызывать в http хендлере не опасаясь заблокировать работу http сервера из-за thread pool depletion
Это не доказательство — завимсит от реализации обработки http.
Давай доказательство на твоем коде.
T>Кривость T>Код по сути идентичный с поправкой на то, что в go используются чуть более низкоуровневые примитивы. Но никто не мешает написать пару хелперов на несколько строк кода и получить абсолютно идентичное решение: https://go.dev/play/p/vV97PJoKyIB
Это вы реализовали функционал async|await, но весьма криво — через каналы. Это раньше приводил вам.