Для чего нужен select без default кейса в Go
Что проверяют
Понимаете ли вы, что select без default это блокирующее ожидание сразу нескольких событий, и умеете ли строить на нём отмену и таймауты. Следом часто просят написать ожидание результата с таймаутом или объяснить, откуда в таком коде утечка горутин.
Как отвечать
Правила работы:
- блокировка: без
defaultгорутина паркуется, пока хотя бы одинcaseне станет готов. Блокируется именно горутина, поток ОС планировщик отдаёт другим горутинам; - выбор при нескольких готовых: равновероятный псевдослучайный, порядок
caseв коде приоритета не даёт; - nil-канал: операция с ним никогда не готова, поэтому, присвоив каналу
nil, можно отключитьcaseна лету; select {}: блокирует навсегда, встречается вmain, когда вся работа идёт в других горутинах;- с
default:selectстановится неблокирующим, это попытка отправить или прочитать без ожидания.
Главный сценарий без default: ждать результат, отмену и таймаут одновременно.
select {
case res := <-resultCh:
return res, nil
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(2 * time.Second):
return nil, errTimeout
}Так строятся воркеры с ctx.Done(), fan-in из нескольких каналов, graceful shutdown по сигналу. Про time.After в цикле стоит сказать, что начиная с Go 1.23 неостановленные таймеры собирает GC, но в горячем цикле всё равно лучше один time.NewTimer с Reset.
Где спотыкаются
Добавляют default в цикл «на всякий случай». Получается busy loop, который крутит ядро процессора вместо того, чтобы спать до события.
Забывают про отправителя. Если по таймауту вышли из select, горутина, которая пишет в небуферизованный resultCh, зависнет навсегда. Канал с буфером на одно значение решает проблему.
Пишут break внутри case, ожидая выйти из внешнего for. break выходит только из select, для цикла нужна метка или return.