Ответ
errgroup — это пакет (golang.org/x/sync/errgroup), который предоставляет средства для синхронизации группы горутин и централизованной обработки ошибок между ними.
Он решает распространенную задачу: запустить несколько параллельных операций, дождаться их завершения и, если хотя бы одна из них вернет ошибку, немедленно отменить остальные и вернуть первую возникшую ошибку.
Ключевые возможности:
- Синхронизация: Метод
g.Wait()блокирует выполнение, пока все запущенные черезg.Go()горутины не завершатся (аналогичноsync.WaitGroup). - Распространение ошибки:
g.Wait()возвращает первую не-nilошибку от любой из горутин. - Отмена через контекст: При использовании
errgroup.WithContext(), если одна горутина возвращает ошибку, контекст, переданный в другие горутины, автоматически отменяется. Это позволяет им грациозно завершить свою работу, не выполняя лишних действий.
Пример: параллельное выполнение HTTP-запросов
func fetchURLs(ctx context.Context, urls []string) error {
g, gCtx := errgroup.WithContext(ctx)
for _, url := range urls {
// Захватываем переменную url в замыкании
url := url
g.Go(func() error {
req, err := http.NewRequestWithContext(gCtx, http.MethodGet, url, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
// Если gCtx был отменен (из-за ошибки в другой горутине),
// http.Client вернет ошибку, и эта горутина завершится.
if err != nil {
return err
}
resp.Body.Close()
fmt.Printf("Fetched %s with status %sn", url, resp.Status)
return nil
})
}
// Ждем завершения всех горутин и возвращаем первую ошибку, если она была.
return g.Wait()
}
errgroup является более мощной и удобной альтернативой sync.WaitGroup, когда требуется не просто дождаться завершения горутин, но и управлять их отменой и обрабатывать ошибки.
Ответ 18+ 🔞
А, слушай, про errgroup! Это же та самая штука, которая превращает твой код из "запустил десять горутин и молишься, чтобы они все сами как-то завершились" в "взрослый, управляемый бардак".
Представь себе: ты на кухне, готовишь обед. Кастрюли на всех конфорках, духовка греется, в микроволновке что-то пищит. И вот одна кастрюля, сука, убегает. По старинке ты бы бегал, выключал всё по отдельности, ругался. А errgroup — это как умная система, которая видит убегающую кастрюлю и тут же вырубает всю плиту, духовку и даже микроволновку. "Всё, пиздец, процесс пошёл по пизде, дальше готовить бессмысленно".
Короче, что он умеет, этот красавец:
- Запустить кучу дел параллельно — как
sync.WaitGroup. - Поймать первую же ошибку и выдать её тебе, как главную залупу.
- Самое сокровенное: если ты создал его через
WithContext, то при первой же ошибке он автоматом отменяет контекст для всех остальных горутин. Они получают сигнал "ребята, всё, пиздуй, дальше работать не надо" и могут грациозно завершиться, а не тупо висеть.
Вот смотри, как это выглядит в деле (запросы куда-то там):
func fetchURLs(ctx context.Context, urls []string) error {
// Создаём группу с привязанным контекстом. Это ключ!
g, gCtx := errgroup.WithContext(ctx)
for _, url := range urls {
url := url // Важный момент, чтоб в замыкании не наебаться
g.Go(func() error {
// ВНИМАНИЕ! Используем gCtx, а не оригинальный ctx.
// Если в другой горутине пиздец, этот контекст отменится,
// и http.Client просто не станет делать запрос или оборвёт его.
req, err := http.NewRequestWithContext(gCtx, http.MethodGet, url, nil)
if err != nil {
return err // Вернул ошибку -> триггер на отмену для всех!
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
resp.Body.Close()
fmt.Printf("Успешно сходили на %s, статус: %sn", url, resp.Status)
return nil
})
}
// Ждём. Все завершились? Отлично. Кто-то накосячил? Получи первую ошибку.
return g.Wait()
}
Вот и вся магия. Без этого пришлось бы городить каналы, select-ы, sync.WaitGroup и следить, кто кого отменил. А тут — одна строчка errgroup.WithContext, и ты уже почти архитектор распределённых систем, блядь.
Короче, если делаешь что-то параллельное и там может что-то пойти не так — бери errgroup. Он сэкономит тебе кучу нервов и строк кода. А нервы, они, нахуй, не восстанавливаются.