Что происходит при закрытии канала в Go
Что проверяют
Знаете ли вы точные правила закрытия каналов. Тема даёт панику в проде чаще других, поэтому спрашивают почти на каждом собеседовании по Go.
Как отвечать
close(ch) сообщает получателям, что данных больше не будет. Дальше правила:
- чтение из закрытого канала возвращает нулевое значение типа немедленно, без блокировки. Отличить его от настоящего нуля помогает вторая переменная:
v, ok := <-ch, гдеok == falseозначает, что канал закрыт и опустошён; - буферизованный канал сначала отдаёт всё, что в нём лежит, и только потом начинает возвращать нули;
for rangeпо каналу сам завершается при закрытии, это самый частый способ дождаться конца потока данных;- отправка в закрытый канал вызывает панику;
- повторное закрытие тоже паника, как и
closeнаnil-канале; - в
selectзакрытый канал всегда готов к чтению, поэтому ветка с ним начнёт срабатывать в цикле. Обычно после закрытия такой канал обнуляют, чтобы ветка перестала выбираться.
Отсюда главное правило владения: закрывает всегда отправитель, и только один. Получатель не закрывает никогда, потому что не знает, пишет ли кто-то ещё. Если отправителей несколько, их завершение ждут через sync.WaitGroup, и закрывает канал тот, кто дождался всех.
go func() {
defer close(ch) // закрывает владелец записи
for _, v := range items { ch <- v }
}()
for v := range ch { use(v) } // цикл завершится самДля сигнала об отмене канал обычно не пишут, а именно закрывают: закрытие видят сразу все получатели. На этом же построен context.Done().
Где спотыкаются
Закрывают канал в получателе и ловят панику на отправителе.
Проверяют закрытие сравнением с нулём вместо ok.
Пытаются понять «закрыт ли канал» перед отправкой. Способа без гонки нет, вопрос решается владением: тот, кто пишет, тот и закрывает.