← Назад к вопросам

Приведи пример использования Wait Group

2.0 Middle🔥 151 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Пример использования WaitGroup в Go

WaitGroup из пакета sync — это один из основных примитивов синхронизации в Go, который позволяет дождаться завершения группы горутин. Это особенно полезно, когда нужно выполнить несколько параллельных задач и синхронизировать их завершение перед продолжением работы основной программы.

Базовый пример использования

Рассмотрим классический пример, где мы запускаем несколько горутин для выполнения задач и используем WaitGroup для ожидания их завершения:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    // Создаем экземпляр WaitGroup
    var wg sync.WaitGroup
    
    // Количество горутин, которые мы планируем запустить
    workerCount := 5
    
    // Добавляем количество горутин, которые нужно подождать
    wg.Add(workerCount)
    
    for i := 1; i <= workerCount; i++ {
        // Запускаем горутину
        go worker(i, &wg)
    }
    
    fmt.Println("Основная программа: ожидаю завершения всех горутин...")
    
    // Блокируем выполнение основной программы до завершения всех горутин
    wg.Wait()
    
    fmt.Println("Основная программа: все горутины завершены!")
}

func worker(id int, wg *sync.WaitGroup) {
    // Уменьшаем счетчик WaitGroup при завершении горутины
    defer wg.Done()
    
    fmt.Printf("Воркер %d: начинаю работу\n", id)
    
    // Имитируем выполнение работы
    time.Sleep(time.Duration(id) * time.Second)
    
    fmt.Printf("Воркер %d: завершил работу\n", id)
}

Ключевые моменты использования WaitGroup

  1. Создание и инициализация:

    • var wg sync.WaitGroup создает новый экземпляр WaitGroup
    • Счетчик начинается с 0
  2. Метод Add():

    • Увеличивает счетчик ожидаемых горутин
    • wg.Add(n) добавляет n к счетчику
    • Важно вызывать ДО запуска горутин, чтобы избежать гонок
  3. Метод Done():

    • Уменьшает счетчик на 1
    • Обычно вызывается через defer wg.Done() в начале функции горутины
    • Это гарантирует, что счетчик уменьшится даже при панике
  4. Метод Wait():

    • Блокирует выполнение до тех пор, пока счетчик не станет равным 0
    • После этого программа продолжает выполнение

Более сложный пример с обработкой ошибок

package main

import (
    "errors"
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    var mu sync.Mutex
    var errorsList []error
    
    tasks := []string{"task1", "task2", "task3", "task4", "task5"}
    
    wg.Add(len(tasks))
    
    for _, task := range tasks {
        go func(t string) {
            defer wg.Done()
            
            // Выполняем задачу
            err := processTask(t)
            
            if err != nil {
                // Безопасно добавляем ошибку в общий список
                mu.Lock()
                errorsList = append(errorsList, err)
                mu.Unlock()
            }
        }(task)
    }
    
    wg.Wait()
    
    // Обрабатываем результаты
    if len(errorsList) > 0 {
        fmt.Printf("Найдены ошибки: %v\n", errorsList)
    } else {
        fmt.Println("Все задачи выполнены успешно!")
    }
}

func processTask(task string) error {
    // Имитация обработки задачи
    if task == "task3" {
        return errors.New("ошибка в задаче task3")
    }
    fmt.Printf("Задача %s выполнена\n", task)
    return nil
}

Важные рекомендации по использованию

  • Всегда передавайте WaitGroup по указателю: Если передать WaitGroup по значению, счетчик будет изменяться в копии, что приведет к deadlock
  • Используйте defer для wg.Done(): Это гарантирует вызов даже при досрочном возврате из функции или панике
  • Планируйте количество горутин заранее: По возможности вызывайте wg.Add() один раз с общим количеством горутин
  • Избегайте вызова Add() внутри горутин: Это может привести к гонкам и непредсказуемому поведению
  • Комбинируйте с другими примитивами: Для безопасного доступа к общим данным используйте мьютексы или каналы

Альтернативный подход с анонимными функциями

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    
    urls := []string{
        "https://example.com/1",
        "https://example.com/2",
        "https://example.com/3",
    }
    
    results := make([]string, len(urls))
    
    for i, url := range urls {
        wg.Add(1)
        
        go func(idx int, u string) {
            defer wg.Done()
            
            // Имитация HTTP-запроса
            results[idx] = fmt.Sprintf("Ответ от %s", u)
        }(i, url)
    }
    
    wg.Wait()
    
    for _, result := range results {
        fmt.Println(result)
    }
}

WaitGroup — это простой, но мощный инструмент для синхронизации горутин. Он идеально подходит для сценариев, где нужно дождаться завершения набора независимых задач. Однако для более сложных сценариев взаимодействия между горутинами часто лучше подходят каналы или другие примитивы синхронизации из пакета sync.