Материал проходит вычитку — возможны неточности.
Урок 79 из 118

close и ok

Шаг разбора закончил пакет и закрыл свой канал — следующий шаг сразу понял, что файлов больше не будет. Миша записал в заметки простое правило: канал закрывал тот, кто в него писал. С этого момента каждый шаг точно знал, когда партия закончилась.

Главная мысль

закрытие канала — сигнал «данных больше не будет»: приём из закрытого сначала отдаёт буферизованные значения с ok == true, после опустошения буфера — zero value и ok == false; отправка в закрытый и повторное закрытие паникуют, а закрывает канал отправитель.

Закрытие как сигнал конца

ch := make(chan File)

go func() { // шаг-производитель
    for _, f := range batch {
        ch <- f
    }
    close(ch) // файлов больше не будет
}()

for {
    f, ok := <-ch
    if !ok {
        break
    }
    process(f)
}

close(ch) закрывает канал. Приём из закрытого канала не блокируется: если в буфере остались значения, он сначала отдаёт их с ok == true, и только после опустошения буфера возвращает zero value и ok == false. Поэтому ok == true означает «получено реально отправленное значение», а ok == false — «канал закрыт и буфер пуст».

С буферизованным каналом это особенно заметно — zero value придёт не сразу, а только после остатков:

ch := make(chan int, 1)
ch <- 7
close(ch)

v1, ok1 := <-ch // 7, true  — остаток буфера
v2, ok2 := <-ch // 0, false — буфер пуст, канал закрыт

Что нельзя делать с закрытым каналом

  • Отправка в закрытый канал — паника.
  • Закрытие закрытого канала — паника.
  • Приём из закрытого — безопасен: сначала остатки буфера с ok == true, затем zero value с ok == false.
ch := make(chan int)
close(ch)
ch <- 1   // panic: send on closed channel
close(ch) // panic: close of closed channel

Кто закрывает

Каноническое правило: закрывает отправитель. Логика проста — он знает, когда данные закончились. Получателю закрывать канал неестественно: если канал читают несколько горутин, одна из них может закрыть его под отправкой другой — паника.

Есть тонкость с несколькими отправителями: если значения в один канал шлют несколько горутин, закрытие из одной из них опасно — другая может отправить в уже закрытый канал. Решение — выделить закрытие в отдельную горутину, которая знает, когда все отправители закончили (WaitGroup), либо не закрывать канал вовсе и использовать другой механизм завершения. Вопрос «кто закрывает» стоит задавать при проектировании каждого канала — и каждого шага конвейера.

В следующем уроке

Вы узнаете:

  • как закрытие небуферизованного канала сигналит «сделано»
  • как избегать паники на отправке в закрытый при нескольких отправителях
<!-- wp:course-hub/quiz {"title":"Проверьте себя","questions":[{"text":"ch := make(chan int, 1); ch <- 7; close(ch). Что даст первый и второй приём?","options":[{"text":"7 true, затем 0 false — сначала остатки буфера с ok=true, после опустошения zero value","correct":true,"comment":"ok == true — реально отправленное значение; ok == false — канал закрыт и буфер пуст."},{"text":"7 true, затем 7 true","correct":false},{"text":"0 false, затем 0 false","correct":false},{"text":"Паника","correct":false}]},{"text":"ch := make(chan int); close(ch); ch <- 1. Что произойдёт?","options":[{"text":"panic: send on closed channel","correct":true,"comment":"Повторное закрытие — panic: close of closed channel; приём из закрытого безопасен."},{"text":"Значение отправится","correct":false},{"text":"Вернёт 0","correct":false},{"text":"Ошибка компиляции","correct":false}]},{"text":"Почему case приёма на закрытом канале в select — ловушка?","options":[{"text":"Он готов всегда — без проверки ok цикл бесконечно читает zero value","correct":true,"comment":"Нужно case v, ok :=
Итоги

close — сигнал конца от отправителя; приём из закрытого сначала отдаёт остатки буфера с ok=true, затем zero value с ok=false; отправка в закрытый и повторное закрытие — паника.

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки