Что такое конструкция select в Go и зачем она нужна
Что проверяют
Понимаете ли вы, что select ждёт сразу несколько каналов, и знаете ли поведение default и нулевых каналов.
Как отвечать
select блокируется, пока не будет готова хотя бы одна из перечисленных операций с каналами, и выполняет соответствующую ветку. Если готовы несколько, выбирается случайная, чтобы не было систематического голодания.
select {
case msg := <-ch:
handle(msg)
case <-ctx.Done():
return ctx.Err()
case <-time.After(2 * time.Second):
return errTimeout
}Зачем нужен: ожидание нескольких источников сразу, отмена по контексту, таймаут, объединение потоков данных, неблокирующая проверка.
Правила, которые проверяют:
- default делает
selectнеблокирующим: не готова ни одна ветка, выполняется он. Отсюда неблокирующая отправка и чтение; - пустой
select {}блокируется навсегда; - нулевой канал никогда не готов, поэтому присваивание
ch = nilэто идиоматичный способ выключить ветку внутри цикла, когда источник исчерпан; - закрытый канал всегда готов к чтению и отдаёт нулевое значение, поэтому ветку с ним нужно выключать, иначе цикл начнёт крутиться вхолостую.
Отдельно про таймауты: time.After в цикле создаёт новый таймер на каждой итерации, и при частых итерациях это заметный мусор. В таком случае используют time.NewTimer с переиспользованием.
Где спотыкаются
Ставят default в цикл ожидания и получают активное ожидание, которое сжигает ядро.
Не выключают ветку с закрытым каналом и попадают в бесконечный цикл.
Считают, что ветки проверяются сверху вниз по порядку.
Забывают ветку ctx.Done() и получают горутину, которую нельзя остановить.