Что такое WaitGroup
sync.WaitGroup — это структура из пакета sync в Go, предназначенная для ожидания завершения набора горутин. Она позволяет главной горутине блокироваться до тех пор, пока все запущенные горутины не завершат выполнение, что необходимо для корректной синхронизации параллельных задач.
Как это работает
WaitGroup использует внутренний счётчик. При создании он равен нулю. Метод Add(delta) увеличивает счётчик на delta (обычно перед запуском горутин). Метод Done() уменьшает счётчик на единицу и вызывается в конце каждой горутины. Метод Wait() блокирует выполнение до тех пор, пока счётчик не станет нулевым. Когда счётчик достигает нуля, все ожидающие горутины разблокируются.
Важно: Add должен вызываться до Wait, иначе возможна гонка данных. Рекомендуется вызывать Add в главной горутине до запуска горутин, а Done — через defer внутри горутины, чтобы гарантировать уменьшение счётчика даже при панике.
Пример
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // гарантированно уменьшаем счётчик при выходе
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second) // имитация работы
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
numWorkers := 3
wg.Add(numWorkers) // устанавливаем счётчик на 3
for i := 1; i <= numWorkers; i++ {
go worker(i, &wg) // запускаем горутины
}
wg.Wait() // ждём завершения всех горутин
fmt.Println("All workers completed")
}Здесь wg.Add(3) задаёт счётчик, каждая горутина вызывает Done() через defer, а Wait() блокирует main до тех пор, пока все три горутины не завершатся.
Подводные камни
- Копирование
WaitGroup: структуру нельзя копировать после использования, так как это приведёт к некорректной работе счётчика. Передавайте её по указателю. - Вызов
Addвнутри горутины: еслиAddвызывается внутри горутины, аWaitуже запущен, возможна гонка. Лучше вызыватьAddдо запуска горутин. - Отрицательный счётчик: если
Doneвызывается больше раз, чемAdd, возникает паника. Следите за балансом. - Использование с
defer: всегда используйтеdefer wg.Done()в начале горутины, чтобы избежать пропуска при раннем выходе.
Когда использовать
WaitGroup подходит для сценариев, где нужно дождаться завершения фиксированного набора горутин, например, при параллельной обработке данных или ожидании завершения фоновых задач. Для динамических наборов задач или необходимости получать результаты лучше использовать каналы или errgroup.
Коротко
WaitGroupсинхронизирует горутины через счётчик:Addувеличивает,Doneуменьшает,Waitблокирует до нуля.- Передавайте
WaitGroupпо указателю, не копируйте. - Вызывайте
Addдо запуска горутин,Done— черезdefer. - Используйте для ожидания завершения фиксированного числа горутин; для более сложных сценариев — каналы или
errgroup.
