Реализуйте собственный аналог sync.WaitGroup с тем же интерфейсом:
type MyWaitGroup struct { /* ... */ }
func (wg *MyWaitGroup) Add(delta int)
func (wg *MyWaitGroup) Done()
func (wg *MyWaitGroup) Wait()
Add(delta) изменяет счётчик незавершённых задач на delta (может быть и отрицательной).
Done() уменьшает счётчик на единицу, то есть то же, что Add(-1).
Wait() блокируется, пока счётчик не станет равным нулю. Если он уже ноль, Wait возвращается сразу.
- Если счётчик ушёл в минус, это ошибка вызывающего: как и в настоящем
WaitGroup, нужно вызвать панику.
Ограничение: нельзя использовать sync.WaitGroup и пакет sync/atomic. Подойдут sync.Mutex, sync.Cond и каналы. Стандартный путь: счётчик под мьютексом и sync.Cond, на котором спит Wait. Крутить цикл в ожидании нуля (busy-loop) нельзя.
Пример:
var wg MyWaitGroup // нулевое значение должно быть готово к работе
wg.Add(3)
for i := 0; i < 3; i++ {
go func() {
defer wg.Done()
time.Sleep(10 * time.Millisecond)
}()
}
wg.Wait() // вернётся, когда все три горутины вызвали Done
Тесты: Add(50) и 50 горутин с разной задержкой, Wait обязан вернуться после последней; Wait на нулевом счётчике возвращается сразу; при трёх Done с паузами Wait не возвращается раньше третьего; двадцать ожидающих просыпаются все, и не раньше нуля; уход счётчика в минус вызывает панику. Запрет на sync.WaitGroup, sync/atomic и busy-loop тесты не проверяют.
Сначала попробуй сам — подсказки не дают готовый ответ.