Ответ
errgroup
— это стандартный пакет для синхронизации, который упрощает управление группой горутин, выполняющих подзадачи в рамках одной общей задачи. Он решает три ключевые проблемы:
- Синхронизация: Позволяет дождаться завершения всех запущенных горутин с помощью метода
Wait()
. - Агрегация ошибок:
Wait()
возвращает первую не-nil
ошибку от любой из горутин. Если все завершились успешно, возвращаетnil
. - Отмена через контекст: При использовании
errgroup.WithContext()
создаетсяGroup
вместе с дочерним контекстом. Этот контекст автоматически отменяется, как только одна из горутин возвращает ошибку. Это позволяет остальным горутинам грациозно завершить свою работу, не выполняя лишних действий.
Основные сценарии использования:
- Fan-out: Параллельное выполнение однотипных независимых операций (например, запросы к нескольким внешним API).
- Graceful Shutdown: Координированное завершение нескольких компонентов сервиса (например, остановка HTTP-сервера и закрытие соединения с БД).
- Ограничение параллелизма: С помощью метода
SetLimit(n)
можно ограничить количество одновременно работающих горутин, что полезно для контроля потребления ресурсов.
Пример использования:
Представим, что нам нужно параллельно получить данные о пользователе и его заказах из разных сервисов.
func GetUserAndOrders(ctx context.Context, userID string) (*User, *Orders, error) {
g, ctx := errgroup.WithContext(ctx)
var user *User
g.Go(func() error {
// Важно передавать ctx внутрь, чтобы сработала отмена
var err error
user, err = fetchUser(ctx, userID)
return err // Возвращаем ошибку, если она есть
})
var orders *Orders
g.Go(func() error {
// Проверяем ctx.Done() для быстрой отмены
select {
case <-ctx.Done():
return ctx.Err() // Возвращаем ошибку отмены
default:
}
var err error
orders, err = fetchOrders(ctx, userID)
return err
})
// Ожидаем завершения обеих горутин.
// Если одна из них вернет ошибку, Wait() вернет ее, а контекст будет отменен.
if err := g.Wait(); err != nil {
return nil, nil, err
}
return user, orders, nil
}
Ключевой момент: Внутри горутин всегда нужно проверять состояние контекста (ctx.Done()
), чтобы своевременно прекратить выполнение в случае ошибки в соседней горутине.