Как читать данные из двух каналов одновременно без использования горутин
Что проверяют
Знаете ли вы select и поведение каналов в граничных случаях: закрытый канал, nil канал, пустой канал без отправителей. Формулировка «без горутин» проверяет, понимаете ли вы, что читать можно из одной горутины, но писать кто-то всё равно должен. Следом обычно спрашивают, как добавить таймаут и что делать, если каналов не два, а N.
Как отвечать
Базовый ответ: select в цикле в той же горутине, где идёт чтение. Он ждёт, пока хотя бы один случай станет готов, а если готовы несколько, выбирает между ними случайно, поэтому ни один канал не голодает. Дальше детали, на которых проверяют:
- закрытый канал: чтение из него не блокируется и сразу отдаёт нулевое значение. Проверяйте второе значение в
v, ok := <-ch, иначе цикл закрутится вхолостую; nilканал: операции с ним блокируются навсегда, поэтому вselectтакой случай никогда не выбирается. Закрытому каналу присваиваютch1 = nil, а цикл завершают, когда оба канала сталиnil;- неблокирующее чтение: ветка
defaultсрабатывает, если данных нет прямо сейчас. Без паузы или другой работы в цикле это активное ожидание, которое сжигает CPU; - таймаут и отмена: случай
<-ctx.Done()или таймер. В цикле удобнее одинtime.NewTimerсReset, чем новыйtime.Afterна каждой итерации; - N каналов:
reflect.Selectпринимает срезreflect.SelectCaseи работает с заранее неизвестным числом каналов.
Про «без горутин» скажите прямо. Если в программе нет ни одной другой горутины, данные в каналы кладёт та же горутина, значит каналы обязаны быть буферизованными. Отправка в небуферизованный канал без читателя заблокирует её навсегда, и рантайм остановит программу с ошибкой all goroutines are asleep.
Где спотыкаются
Читают из каналов по очереди: <-ch1, потом <-ch2. Пока первый пуст, второй не читается вовсе, это не одновременное чтение.
Не обрабатывают закрытие. После close случай с этим каналом выбирается на каждой итерации с нулевым значением, и цикл либо крутится впустую, либо пишет мусор.
Ставят break внутри select, рассчитывая выйти из цикла. Он прерывает только select, для выхода нужна метка на for или return.