Код-ревью Senior Паттерны конкурентности спрашивают часто обновлено
Код-ревью Go: параллельная обработка с ошибками
Вопрос
Функция обрабатывает идентификаторы параллельно и возвращает результаты или первую ошибку. Найдите проблемы.
func ProcessAll(ctx context.Context, ids []int) ([]Result, error) {
results := make(chan Result)
errc := make(chan error)
var wg sync.WaitGroup
for _, id := range ids {
wg.Add(1)
go func() {
defer wg.Done()
r, err := process(ctx, id)
if err != nil {
errc <- err
return
}
results <- r
}()
}
wg.Wait()
close(results)
var out []Result
for r := range results {
out = append(out, r)
}
select {
case err := <-errc:
return nil, err
default:
}
return out, nil
}
Коротко
Всё это решает errgroup: Лимит параллелизма, отмена по первой ошибке, порядок по индексу, никаких каналов, которые можно забыть прочитать.
Что должен найти ревьюер
- Производительностьстрока 5Горутина на каждый id без ограничения: на большом входе это перегрузит базу или внешний сервис. Нужен лимит (SetLimit, семафор, пул).
- Ошибкастрока 17Дедлок: горутины блокируются на отправке в небуферизованный results, потому что читать начнут только после wg.Wait(), а Wait ждёт эти же горутины.
- Ошибкастрока 11Небуферизованный errc: читают из него максимум один раз и только неблокирующе, поэтому горутины с ошибками повиснут навсегда. Утечка горутин.
- Производительностьстрока 9После первой ошибки остальные задачи продолжают работать: нет отмены общего контекста.
- Ошибкастрока 21Результаты собираются в порядке завершения, а не в порядке ids: вызывающий не сможет сопоставить результат и id.
Разбор
Всё это решает errgroup:
func ProcessAll(ctx context.Context, ids []int) ([]Result, error) {
out := make([]Result, len(ids))
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(16)
for i, id := range ids {
g.Go(func() error {
r, err := process(ctx, id)
if err != nil {
return fmt.Errorf("process %d: %w", id, err)
}
out[i] = r
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return out, nil
}
Лимит параллелизма, отмена по первой ошибке, порядок по индексу, никаких каналов, которые можно забыть прочитать.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.