Что такое `errgroup` и для каких задач он используется?

Ответ

errgroup — это пакет из golang.org/x/sync/errgroup, который предоставляет средства для синхронизации, распространения ошибок и отмены контекста для группы горутин, выполняющихся параллельно.

Он упрощает распространенный паттерн, когда нужно запустить несколько фоновых задач, дождаться их завершения и корректно обработать первую возникшую ошибку.

Основные компоненты и принцип работы:

  • errgroup.Group: Основная структура, управляющая группой горутин.
  • g.Go(func() error): Запускает переданную функцию в новой горутине. Если функция возвращает ошибку, errgroup запоминает её.
  • g.Wait() error: Блокирует выполнение до тех пор, пока все горутины, запущенные через g.Go(), не завершатся. Возвращает первую не-nil ошибку, полученную от любой из горутин.
  • errgroup.WithContext(): Создает Group и связанный с ней context.Context. Этот контекст автоматически отменяется, как только одна из горутин возвращает ошибку. Это позволяет остальным горутинам грациозно завершить свою работу.

Пример: Выполнение нескольких HTTP-запросов параллельно.

package main

import (
    "context"
    "fmt"
    "net/http"

    "golang.org/x/sync/errgroup"
)

func main() {
    // Создаем группу, связанную с контекстом
    g, ctx := errgroup.WithContext(context.Background())

    urls := []string{
        "http://www.golang.org/",
        "http://www.google.com/",
        "http://invalid-url/", // Эта задача вернет ошибку
    }

    for _, url := range urls {
        url := url // https://golang.org/doc/faq#closures_and_goroutines
        // Запускаем задачу в горутине
        g.Go(func() error {
            // Создаем запрос, который уважает отмену контекста
            req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
            if err != nil {
                return err
            }
            resp, err := http.DefaultClient.Do(req)
            if err == nil {
                resp.Body.Close()
            }
            fmt.Printf("Fetched %s with status: %sn", url, resp.Status)
            return err
        })
    }

    // Ждем завершения всех горутин и проверяем ошибку
    if err := g.Wait(); err != nil {
        fmt.Printf("nAn error occurred: %vn", err)
    }

    fmt.Println("All tasks finished.")
}