Ответ
Да, счетчик 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
.