Ответ
sync.WaitGroup
— это механизм синхронизации, который позволяет основной горутине ожидать завершения выполнения набора других (дочерних) горутин.
Основные методы:
Add(delta int)
Увеличивает внутренний счетчик активных горутин наdelta
. Важно вызывать этот метод в основной горутине до запуска дочерней горутины, чтобы избежать состояния гонки.Done()
Уменьшает счетчик на единицу. Обычно вызывается в конце работы горутины с помощьюdefer
, чтобы гарантировать выполнение даже в случае паники внутри горутины.Wait()
Блокирует горутину, в которой был вызван, до тех пор, пока внутренний счетчик не станет равен нулю.
Пример использования:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Уменьшаем счетчик при выходе из функции
fmt.Printf("Worker %d startingn", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d donen", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // Увеличиваем счетчик перед запуском горутины
go worker(i, &wg)
}
fmt.Println("Main: Waiting for workers to finish...")
wg.Wait() // Ожидаем, пока счетчик не станет 0
fmt.Println("Main: All workers finished. Exiting.")
}
Ключевые правила и частые ошибки:
- Вызов
Add
до запуска горутины: Если вызватьwg.Add(1)
внутри самой горутины,wg.Wait()
может сработать раньше, чемAdd
выполнится, что приведет к преждевременному завершению программы. - Отрицательный счетчик: Количество вызовов
Done()
не должно превышать количество, указанное вAdd
. Это вызовет паникуpanic: sync: negative WaitGroup counter
. - Копирование
WaitGroup
:WaitGroup
нельзя копировать после первого использования. Ее следует передавать по указателю (*sync.WaitGroup
). - Переиспользование: После того как
Wait()
отработал и счетчик обнулился,WaitGroup
можно использовать заново для новой группы горутин, снова вызвавAdd
.