Ответ
Для параллельного выполнения задач и ожидания их завершения в 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 за раз) можно использовать буферизованный канал в качестве семафора.
Ответ 18+ 🔞
Слушай, а вот этот твой код с горутинами — это ж классика, блядь! Как эти все вэб-сайты дергать параллельно, чтобы не ждать, пока один за другим, как мудаки, загрузятся.
Ну, первое, что в голову приходит — это sync.WaitGroup. Представь себе, это как такой счетчик, сука. Ты ему говоришь: «Слушай, я сейчас 4 горутины запущу». Он такой: «Ага, окей, wg.Add(4)». А потом каждая горутина, когда свою хуйню сделала, кричит: «Я всё, свободна!» — wg.Done(). А главная горутина сидит на wg.Wait(), как дура, и не двигается дальше, пока этот счетчик в ноль не упадет. Пиздец просто, но работает.
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1) // Щас ещё одна побежит, считай её!
go func(u string) {
defer wg.Done() // Всё, приехали, я отстрелялась. Обязательно через defer, а то забудешь, кретин!
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.")
Но это, блядь, для простых случаев. А если тебе ещё и ошибки ловить, и чтобы при первой же ошибке все остальные горутины пошли нахуй? Тут в игру вступает errgroup. Это такая обёрточка, ёпта, просто песня!
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) // Поймали первую же ошибку — и всем пиздец.
}
Ах да, самый важный момент! Если ты запустишь 100500 горутин на 100500 запросов одновременно, тебя ебанут по IP, или твое приложение сожрёт всю память. Надо ограничивать, сука! Тут тебе в помощь — семафор через буферизованный канал. Создаёшь канал, скажем, на 5 мест. Прежде чем запустить горутину, пытаешься в него что-то запихнуть (sem <- struct{}{}). Если там уже 5 штук — ждёшь, пока одна не освободится. Горутина, завершаясь, выкидывает из канала пустышку (<-sem). И всё, магия! Одновременно только 5 горутин будут пастись. Красота, блядь!