Что произойдёт, если вызвать sync.WaitGroup.Done больше раз, чем было увеличено его значение?
Если вызовов Done окажется больше, чем соответствующих увеличений счётчика через Add, внутренний счётчик WaitGroup станет отрицательным, и Go вызовет панику. Это означает ошибку баланса операций, а не обычное ожидание завершения.
sync.WaitGroup предназначен для координации группы горутин: одна часть программы увеличивает счётчик перед запуском работ, а каждая завершившаяся горутина уменьшает его. Ожидающая сторона блокируется до момента, когда счётчик достигнет нуля.
Такой примитив решает задачу барьера завершения без передачи служебных значений через канал. Он отслеживает количество незавершённых работ, поэтому отрицательное значение не имеет корректного смысла.
Типичная причина ошибки — одновременно использовать defer wg.Done() и явно вызывать Done для той же горутины либо вызвать Done в ветке, для которой счётчик не увеличивался.
Результатом становится паника вида sync: negative WaitGroup counter. Если она не перехвачена в соответствующей горутине, паника завершит весь процесс, поэтому ошибка может остановить сервис, а не только одну задачу.
Каждый логический элемент работы должен иметь ровно одну пару операций: одно увеличение счётчика и одно уменьшение. Увеличение обычно выполняют до запуска горутины, а уменьшение размещают через defer сразу после её начала.
В примере первый Done уменьшает счётчик до нуля, а второй пытается уменьшить его ниже нуля и вызывает панику. WaitGroup не восстанавливает счётчик автоматически и не игнорирует лишний Done.
Важно также не копировать WaitGroup после начала использования: копии имеют независимое представление состояния, и баланс операций становится некорректным. Для повторного использования нужно дождаться завершения предыдущего цикла и обеспечить корректный баланс нового набора работ.
Сервис запускает несколько горутин для обработки файлов. Разработчик добавляет wg.Add(1) перед запуском каждой горутины и использует defer wg.Done(), но затем добавляет ещё один Done в обработчике ошибки, считая его отдельным сигналом завершения.
Вариант с дополнительным Done неверен: ошибка завершает ту же работу, а не создаёт новую. Удаление второго вызова сохраняет простой и надёжный баланс; ручное управление Done в каждой ветке возможно, но повышает риск пропуска или дублирования уменьшения.
Выбранное решение — один Add(1) на одну горутину и один defer wg.Done() внутри неё. В результате ожидание завершается ровно после обработки всех файлов, а паника из-за отрицательного счётчика исключается.
Done после того, как Wait уже разблокировался?Нет, это обычно указывает на ошибку в протоколе. Wait корректно завершается, когда счётчик достигает нуля; последующий Done уменьшит уже нулевой счётчик ниже нуля и вызовет панику.
WaitGroup безопасность при одновременном вызове Add и Wait?Нет, произвольное добавление новых работ одновременно с ожиданием не образует безопасный протокол. Положительный Add, который переводит счётчик с нуля в положительное значение, должен быть выполнен до соответствующего Wait; иначе Wait может увидеть нулевой счётчик и завершиться раньше запуска новой работы.
recover в одной горутине безопасно обработать лишний Done?Технически панику можно перехватить только в той же горутине, где она возникла. Но это не исправляет состояние синхронизации: лишний Done остаётся логической ошибкой, а подавление паники может привести к преждевременному завершению ожидания или скрыть потерю работы. Правильное решение — восстановить баланс Add и Done, а не маскировать последствие.