Ответ
Для ожидания завершения группы горутин в Go используется стандартный и идиоматичный инструмент — sync.WaitGroup.
WaitGroup — это, по сути, счетчик активных горутин. Если main завершится раньше, чем дочерние горутины, они будут принудительно остановлены. WaitGroup позволяет main блокироваться до тех пор, пока все задачи не будут выполнены.
Принцип работы sync.WaitGroup:
wg.Add(n): Увеличивает счетчикWaitGroupнаn. Обычно вызывается перед запуском каждой горутины (wg.Add(1)).wg.Done(): Уменьшает счетчик на 1. Этот вызов должен быть в конце работы горутины. Часто используется сdefer, чтобы гарантировать выполнение, даже если в горутине произойдет паника.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.
Это, блядь, простой как три копейки счётчик. Работает по принципу «ёбушки-воробушки, пока все не вернутся — я никуда».
Как эта мартышлюшка работает:
wg.Add(n): Ты как бы говоришь: «Так, народ, внимание! Я сейчас запущуnработяг, считайте их!». Обычно перед каждой горутиной кричишьwg.Add(1).wg.Done(): Это горутина, закончив своё чёрное дело, кричит: «Я всё, свободна!». Счётчик уменьшается на единичку. Умные люди этоDone()черезdeferставят, чтобы даже если горутина обосрётся с паникой, она всё равно отметилась, что ушла. Порядок, блядь!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 — это как раз та самая простая и быстрая дубина для этой задачи. Не выёбывайся, используй её.