Ответ
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.