Реализуйте собственный аналог sync.WaitGroup со знакомым API:
type MyWaitGroup struct {
// ...
}
func (wg *MyWaitGroup) Add(delta int)
func (wg *MyWaitGroup) Done()
func (wg *MyWaitGroup) Wait()
Add(delta) увеличивает (или уменьшает) счётчик незавершённых задач.
Done() уменьшает счётчик на единицу (эквивалент Add(-1)).
Wait() блокируется до тех пор, пока счётчик не станет равным нулю.
Ограничение: запрещено использовать sync.WaitGroup и напрямую пакет
sync/atomic для счётчика. Реализуйте синхронизацию через каналы и/или
sync.Mutex / sync.Cond. Чистый подход — счётчик под мьютексом плюс
sync.Cond: Wait спит на условной переменной, пока счётчик не дойдёт до
нуля, а Done/Add будят ожидающих через Broadcast.
Тест вызывает Add(N), запускает N горутин, каждая после небольшой
детерминированной задержки вызывает Done. Wait() обязан вернуться только
после того, как все горутины завершились.
На что смотрит интервьюер:
- Корректная синхронизация без
sync.WaitGroup/atomic: счётчик защищён
мьютексом, пробуждение ожидающих через sync.Cond.Broadcast (или закрытие
канала).
Wait действительно блокируется до нуля и просыпается ровно один раз,
без busy-loop и без гонок под -race.
- Понимание паттерна «condition variable»: проверка условия в цикле под
залоченным мьютексом (защита от ложных пробуждений).