select, blocking, default и nil-каналы
select ждёт, пока станет возможна хотя бы одна коммуникация; если готовы несколько, runtime выбирает одну псевдослучайно. Это основной способ объединить канал данных, отмену и таймер, но default и nil-каналы меняют блокирующее поведение и требуют точного контракта.
Зачем это на интервью
Нужно без запуска кода объяснить, почему select с default может крутить CPU, почему операция с nil-каналом никогда не будет выбрана и как безопасно отключать закрытый input в многоканальном цикле.
Минимум для E4
- Понимать, что
selectбез готового case блокируется, если нетdefault. - Знать, что send/receive для nil channel блокируются навсегда, а
close(nil)паникует. - Ставить
ctx.Done()вselectтам, где операция может ждать. - Не использовать
defaultдля polling без backoff или явной причины.
package main
import (
"context"
"fmt"
)
func forward(ctx context.Context, in <-chan int, out chan<- int) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case v, ok := <-in:
if !ok {
return nil
}
select {
case <-ctx.Done():
return ctx.Err()
case out <- v:
}
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
in, out := make(chan int, 1), make(chan int, 1)
in <- 7
close(in)
_ = forward(ctx, in, out)
fmt.Println(<-out)
}Углубление для E5/Senior
Go не обещает строгую fairness: при нескольких готовых cases выбор равномерно псевдослучаен в рамках реализации, но код не должен полагаться на bounded wait конкретной ветви. default превращает ожидание в немедленную проверку; в цикле это часто busy loop и starvation других работ. Для периодической работы используйте ticker, deadline или блокирующее ожидание.
Присваивание local variable nil отключает соответствующий case. Это удобно при объединении нескольких входов: после закрытия input его делают nil, чтобы select не выбирал receive из закрытого канала бесконечно. Но если все каналы стали nil и нет контекста/таймера, select блокируется навсегда; цикл должен явно завершаться.
Ключевые понятия
- Ready case — send с доступным местом/receiver или receive с данными/закрытым каналом.
default— выбирается сразу, когда готовых коммуникаций нет; не «низкий приоритет».- Nil channel — отключённый channel case; коммуникация на нём никогда не готова.
- Closed channel case — receive всегда готов после опустошения буфера, поэтому его нужно выключить или завершить цикл.
- Nested select — способ сделать и receive, и последующий send отменяемыми.
Типовые вопросы
- Когда выполняется
default?- Немедленно, если в момент проверки нет готовых channel cases; select при этом не ждёт будущих событий.
- Как select выбирает из двух готовых cases?
- Псевдослучайно. Нельзя строить алгоритм на гарантированном чередовании или приоритете.
- Что делает nil channel в select?
- Его send/receive case отключён: он никогда не готов. Это отличается от закрытого канала, receive которого готов.
- Почему receive из закрытого канала в select опасен?
- Он постоянно готов и может захватить цикл, возвращая zero values. Обработайте
ok=falseи присвойте каналу nil/выйдите.
- Он постоянно готов и может захватить цикл, возвращая zero values. Обработайте
- Как сделать send отменяемым?
- Обернуть send в
selectсcase <-ctx.Done(); одной проверки context до send недостаточно.
- Обернуть send в
- Можно ли закрыть nil channel?
- Нет,
close(nil)вызывает panic.
- Нет,
Практика
- Объедините два входных канала и отключайте каждый после
ok=false. Готово: программа заканчивается после закрытия обоих и не печатает zero values. - Напишите отправку результата с cancellation. Готово: при остановленном consumer и отменённом context goroutine возвращается, что подтверждает тест с timeout.
- Добавьте
defaultв бесконечный select и измерьте CPU. Готово: объяснён busy loop, исправление использует timer либо блокирующую ветвь.
Частые ошибки и ловушки
- Считать
defaultбезопасным способом «не зависнуть» и получить spin loop. - Путать nil и закрытый канал.
- Забывать cancellation вокруг блокирующего send после успешного receive.
- Ожидать deterministic fairness между cases.
- Оставлять select со всеми nil-каналами без условия выхода.
Связанные темы
Конкурентность · Каналы и close · Pipeline и отмена · Context