Ответ
Да, счетчик sync.WaitGroup можно изменять динамически, но делать это нужно с осторожностью, чтобы избежать паники или состояний гонки.
Основные методы для этого — Add(delta int) для увеличения (или уменьшения) счетчика и Done() для его уменьшения на единицу.
Пример динамического добавления задач:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
// Запускаем 3 начальные горутины
wg.Add(3)
for i := 1; i <= 3; i++ {
go func(id int) {
defer wg.Done()
fmt.Printf("Задача %d выполняетсяn", id)
time.Sleep(100 * time.Millisecond)
}(i)
}
// В процессе выполнения решаем добавить еще 2 задачи
fmt.Println("Динамически добавляем еще 2 задачи...")
wg.Add(2)
for i := 4; i <= 5; i++ {
go func(id int) {
defer wg.Done()
fmt.Printf("Дополнительная задача %d выполняетсяn", id)
time.Sleep(200 * time.Millisecond)
}(i)
}
wg.Wait() // Ожидаем завершения всех 5 задач
fmt.Println("Все задачи выполнены.")
}Важные правила и риски:
- Состояние гонки (Race Condition): Вызывать
Add()нужно до того, как счетчик может стать равен нулю. Еслиwg.Wait()сработает до того, как другая горутина вызоветwg.Add(), программа завершится преждевременно. Лучшая практика — вызыватьAdd()в той же горутине, которая запускает новые дочерние горутины, и делать это до их запуска. - Паника при отрицательном счетчике: Если вызов
Done()илиAdd()с отрицательным значением сделает внутренний счетчик отрицательным, программа запаникует (panic: sync: negative WaitGroup counter). - Паника после
Wait(): Повторное использованиеWaitGroupпосле того, какWait()уже отработал (т.е. счетчик стал 0), небезопасно. Если нужно повторить цикл ожидания, создайте новый экземплярWaitGroup.