Как уведомить горутины в Go о необходимости завершения при получении первого результата
Что проверяют
Умеете ли вы остановить лишнюю работу, а не просто забрать первый ответ и уйти, оставив остальные горутины крутиться. Следом почти всегда спрашивают, что будет с горутинами, которые пытаются отправить результат в канал, когда его уже никто не читает.
Как отвечать
Стандартный путь в современном Go это context.Context:
ctx, cancel := context.WithCancel(parent): общий контекст для всех воркеров,defer cancel()сразу после создания;- воркеры: проверяют
ctx.Done()вselectили передаютctxв вызовы, которые умеют прерываться сами:http.NewRequestWithContext,db.QueryContextи подобные; - канал результатов: буферизованный на число воркеров,
make(chan Result, n). Опоздавшие горутины запишут значение и выйдут, а не повиснут на отправке; - первый результат:
res := <-results, затемcancel(). Отмена закрывает каналDone(), а закрытие канала видят все читатели сразу, в отличие от отправки одного значения; - ожидание выхода:
sync.WaitGroupилиerrgroup.WithContextизgolang.org/x/sync/errgroup, который сам отменяет контекст при первой ошибке.
Без контекста работает тот же принцип: done := make(chan struct{}) и close(done). Контекст удобнее тем, что отмена проходит сквозь стандартную библиотеку и сторонние клиенты. Если буфер у канала не выделен, отправку пишут так: select { case results <- r: case <-ctx.Done(): return }. Плюсом будет упомянуть context.WithCancelCause, он позволяет передать причину отмены.
Где спотыкаются
Берут небуферизованный канал результатов и читают из него одно значение. Остальные горутины блокируются на отправке навсегда, это классическая утечка горутин.
Сообщают об остановке, отправив в канал одно значение. Его получит только одна горутина, а всем сразу сигнал передаёт лишь закрытие канала или cancel().
Проверяют ctx.Done() через select с веткой default, а внутри делают долгий блокирующий вызов без контекста. Горутина реагирует на отмену только между итерациями, и запрос, который уже начался, всё равно доработает до конца.