Comments 16
А как Вы думали оно реализовано? В случайных местах в коде вставлены проверки? :)
В Go вообще не так уж и много происходит за кадром, ассемблер на выходе получается без сюрпризов, отсюда и вывод напрашивающийся, что переключение контекста происходит в особенные моменты в рантайме.
Отличная статья с обзором типовых ошибок.
В очередной раз подтверждается тезис, что независимо от языка нужно писать аккуратно и вдумчиво. И покрывать код тестами.
Ну, и голанг — не серебряная пуля, как некоторые фанаты утверждают.
Go выбирает для себя подход CSP. Этот же подход используется, например, в таком языке, как Erlang.
У erlang модель акторов а не csp
Robert Virding (один из отцов Erlang) про это написал так: "Actually we had never heard of the actor model, not before ppl told us we had implemented it" (https://twitter.com/rvirding/status/766242280592277504)
Более подробное обсуждение можно вот тут почитать: elixirforum.com/t/does-earlier-erlang-concurrency-model-stem-from-csp/16905
Спасибо за ваше замечание, я поправлю текст!)
Основная проблема с решениями вроде errgroup
и прочими заключается в том, что в Go все эти библиотеки — second class citizens.
С точки зрения языка нет же никакой разницы между стандартной библиотекой и внешними пакетами. Или речь не об этом?
Проблема не в инструменте, а отказе думать. Чтобы написать хорошо, нужно выбрать подходящий подход.
Если мы пишем на Go, надо использовать всю мощь языка. Пробуем. Получаем в три раза больше кода.
И показываете пример мощи WaitGroup. Вы незаметно отождествили "мощь Go" и "мощь WaitGroup".
Если утилита синхронизации неудобная, нужно выбрать подходящую. Go не сделает выбор за программиста.
В последнем примере последовательный код решает одну задачу – выполнение запросов, – а конкуретный две – выполнение запросов и их оркестрация. Поэтому конкурентный вариант выглядит справедливо сложнее последовательного.
Если оставить оркестрацию на ответственность вызывающего, код станет проще:
func call(ctx context.Context, requests <-chan Request, errors chan<- error) {
for r := range requests {
go func(r Request) {
if e := send(ctx, r); e != nil {
errors <- e
}
}(r)
}
}
Я ошибся в том, что в моём коде вызывающий может оркестрировать выполнение запросов (не может, хотя это можно доработать), но главное утверждение всё ещё верно: в примере конкурентный код сложнее, потому что выполняет две задачи – отправку запросов и оркестрирование, – и если не делать оркестрирование, то последовательный и конкурентный код будут не особо отличаться как по объёму, так и по размеру.
Как не ошибиться с конкурентностью в Go