Как в функции `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 является более простым, производительным и предназначенным именно для этой задачи решением.

Ответ 18+ 🔞

Слушай, а вот этот твой sync.WaitGroup — это же просто гениальная хуйня, внатуре! Представь себе: у тебя main-функция, такая вот царь-батюшка, а вокруг него суетится дохуя горутин-работяг. И если царь-батюшка решит, что ему пора — хлоп! — и все работяги нахуй отправляются, даже если они там полдела не доделали. Так вот, чтобы такого пиздеца не было, и придумали эту штуку — WaitGroup.

Это, блядь, простой как три копейки счётчик. Работает по принципу «ёбушки-воробушки, пока все не вернутся — я никуда».

Как эта мартышлюшка работает:

  1. wg.Add(n): Ты как бы говоришь: «Так, народ, внимание! Я сейчас запущу n работяг, считайте их!». Обычно перед каждой горутиной кричишь wg.Add(1).
  2. wg.Done(): Это горутина, закончив своё чёрное дело, кричит: «Я всё, свободна!». Счётчик уменьшается на единичку. Умные люди это Done() через defer ставят, чтобы даже если горутина обосрётся с паникой, она всё равно отметилась, что ушла. Порядок, блядь!
  3. wg.Wait(): А это наш царь-батюшка (main) садится на бутылку и говорит: «Я буду сидеть и ждать тут, пока счётчик не станет ноль. Пока все работяги не отзовутся — я ни шагу!».

Смотри, как это выглядит в коде, ёпта:

package main

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

func worker(id int, wg *sync.WaitGroup) {
    // defer — это наше всё. Гарантия, что мы крикнем "Done!", даже если нас тут ебанёт ошибкой.
    defer wg.Done()

    fmt.Printf("Работяга %d зашёл в цехn", id)
    time.Sleep(time.Second) // Делаем вид, что пашем
    fmt.Printf("Работяга %d закончил и пошёл пить чайn", id)
}

func main() {
    // Рождаем нашего сторожа
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        // Кричим сторожу: "Эй, ещё один пошёл!"
        wg.Add(1)
        // И запускаем работягу, дав ему на подержание указатель на сторожа
        go worker(i, &wg)
    }

    fmt.Println("Главный: Сижу, курю, жду когда все отпинаются...")
    // Главный тупо блокируется тут, пока wg.Wait() не скажет "всё, ноль!"
    wg.Wait()

    fmt.Println("Главный: О, все на месте! Ну я пошёл тогда, пизда.")
}

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