Каналы и select: передача данных и координация событий
Содержание курса
close, идиома v, ok и range по каналу: распознавание закрытия и обход до конца
Канал не закрывается сам по себе. Отправляющая сторона явно вызывает close(ch) — это сигнал: «новых значений не будет». После закрытия получатели могут дочитать то, что уже лежит в буфере, а потом приём начнёт возвращать нулевое значение типа.
Чтобы отличить «получил реальное значение» от «канал закрыт и пуст», используется двойное присваивание:
v, ok := <-ch
if !ok {
// канал закрыт, v == нулевое значение типа
}
ok == true означает, что значение пришло от отправителя. ok == false — канал закрыт и опустел. Важно: даже после close приём с ok == true возможен, пока в буфере что-то есть.
Цикл for v := range ch делает именно это в цикле — читает, пока ok == true, и завершается, когда канал закрыт и буфер пуст. Это удобнее ручной проверки ok:
ch := make(chan int, 3)
ch <- 10
ch <- 20
ch <- 30
close(ch)
for v := range ch {
fmt.Println(v) // 10, 20, 30
}
// после range программа продолжается здесь
Ключевой момент: range не завершится, если канал никогда не закрыт. Горутина зависнет навсегда. Поэтому close — это не опция, а обязательство отправителя перед всеми, кто обходит канал через range.
Правило владения: close вызывает тот, кто отправляет — и только он один. Получатель не знает, есть ли ещё другие отправители, поэтому он физически не может принять решение о закрытии. Если попытаться закрыть канал дважды или отправить в уже закрытый:
close(ch)
close(ch) // паника: close of closed channel
ch <- 1 // паника: send on closed channel
Обе паники — рантаймовые, не компиляторные. Go не отслеживает состояние канала статически. Дисциплина «один владелец — одно закрытие» — архитектурная договорённость, которую программист соблюдает вручную.
