Ответ
Для параллельного выполнения задач и ожидания их завершения в Go чаще всего используются горутины в связке с sync.WaitGroup
.
1. Классический подход: sync.WaitGroup
WaitGroup
— это счетчик, который блокирует выполнение до тех пор, пока его значение не станет равным нулю.
wg.Add(n)
: Увеличивает счетчик наn
.wg.Done()
: Уменьшает счетчик на 1 (обычно вызывается вdefer
).wg.Wait()
: Блокирует горутину, пока счетчик не станет 0.
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
urls := []string{
"https://ya.ru",
"https://google.com",
"https://bing.com",
"https://duckduckgo.com",
}
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1) // Увеличиваем счетчик перед запуском горутины
go func(u string) {
defer wg.Done() // Уменьшаем счетчик по завершении
resp, err := http.Get(u)
if err != nil {
fmt.Printf("Error fetching %s: %vn", u, err)
return
}
fmt.Printf("Got %s with status %sn", u, resp.Status)
resp.Body.Close()
}(url) // Важно передать url как аргумент, чтобы избежать проблем с замыканием!
}
wg.Wait() // Ожидаем завершения всех горутин
fmt.Println("All requests finished.")
}
2. Продвинутый подход: errgroup
Пакет golang.org/x/sync/errgroup
упрощает работу с группами горутин, особенно когда нужна обработка ошибок и отмена контекста.
- Автоматически управляет
WaitGroup
. - Возвращает первую возникшую ошибку.
- Позволяет отменить все остальные операции через
context
при возникновении ошибки.
// ... импорты
import (
"context"
"golang.org/x/sync/errgroup"
)
// ... в функции main
g, ctx := errgroup.WithContext(context.Background())
for _, url := range urls {
u := url // Создаем локальную копию для горутины
g.Go(func() error {
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to get %s: %w", u, err)
}
// ... обработка ответа
resp.Body.Close()
return nil
})
}
if err := g.Wait(); err != nil {
fmt.Printf("An error occurred: %vn", err)
}
Для ограничения количества одновременных запросов (например, не более 5 за раз) можно использовать буферизованный канал в качестве семафора.