Конкурентность: горутины, каналы и контекст

Каналы и 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 не отслеживает состояние канала статически. Дисциплина «один владелец — одно закрытие» — архитектурная договорённость, которую программист соблюдает вручную.