Ответ
Разница в управлении счетчиком ожидания WaitGroup
.
В цикле:
var wg sync.WaitGroup
for _, task := range tasks {
wg.Add(1) // Увеличиваем счетчик перед запуском каждой горутины
go func(t Task) {
defer wg.Done()
process(t)
}(task)
}
wg.Wait()
Плюсы:
- Точно отражает количество запущенных горутин, даже если
tasks
изменяется динамически. - Безопасно, если количество задач неизвестно заранее или определяется в процессе.
За циклом:
var wg sync.WaitGroup
wg.Add(len(tasks)) // Увеличиваем счетчик один раз
for _, task := range tasks {
go func(t Task) {
defer wg.Done()
process(t)
}(task)
}
wg.Wait()
Плюсы:
- Меньше накладных расходов (однократный вызов
Add
вместо N вызовов). - Подходит, если количество задач известно и фиксировано до запуска горутин.
Опасность: Если len(tasks)
изменится (например, задачи добавляются или удаляются) после вызова wg.Add(len(tasks))
, но до запуска всех горутин, счетчик может стать некорректным. Это может привести к зависанию (Wait
никогда не вернется) или панике (Done
вызван больше раз, чем Add
).