Как в функции `main` дождаться завершения всех запущенных горутин?

Ответ

Для ожидания завершения группы горутин в Go используется стандартный и идиоматичный инструмент — sync.WaitGroup.

WaitGroup — это, по сути, счетчик активных горутин. Если main завершится раньше, чем дочерние горутины, они будут принудительно остановлены. WaitGroup позволяет main блокироваться до тех пор, пока все задачи не будут выполнены.

Принцип работы sync.WaitGroup:

  1. wg.Add(n): Увеличивает счетчик WaitGroup на n. Обычно вызывается перед запуском каждой горутины (wg.Add(1)).
  2. wg.Done(): Уменьшает счетчик на 1. Этот вызов должен быть в конце работы горутины. Часто используется с defer, чтобы гарантировать выполнение, даже если в горутине произойдет паника.
  3. wg.Wait(): Блокирует выполнение той горутины, в которой он вызван (обычно в main), до тех пор, пока счетчик WaitGroup не станет равен нулю.

Пример:

package main

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

func worker(id int, wg *sync.WaitGroup) {
    // defer гарантирует, что Done() будет вызван при выходе из функции
    defer wg.Done()

    fmt.Printf("Worker %d startingn", id)
    time.Sleep(time.Second) // Симуляция работы
    fmt.Printf("Worker %d donen", id)
}

func main() {
    // Создаем экземпляр WaitGroup
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        // Увеличиваем счетчик перед запуском горутины
        wg.Add(1)
        // Запускаем горутину, передавая указатель на wg
        go worker(i, &wg)
    }

    fmt.Println("Main: Waiting for workers to finish...")
    // Ожидаем, пока счетчик не обнулится
    wg.Wait()

    fmt.Println("Main: All workers have finished. Exiting.")
}

Важно: Использование каналов для этой цели также возможно (например, каждая горутина отправляет сигнал в канал по завершении), но sync.WaitGroup является более простым, производительным и предназначенным именно для этой задачи решением.