Ответ
Да, счетчик 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.
Ответ 18+ 🔞
А, ну, вот этот ваш 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, счетчик ноль. Всё, пизда, приехали. Он одноразовый, понимаешь? Нельзя его, блядь, как старую перчатку, использовать повторно. Хочешь новый цикл — создавай новый экземпляр, и никаких геморроев.