Будет ли паника при чтении из закрытого канала в Go
Что проверяют
Знаете ли вы точную семантику каналов, а не общее «с закрытым каналом что-то плохое». Правильный ответ: при чтении паники нет. Следом спрашивают, какие операции всё-таки паникуют и как отличить закрытый канал от присланного нуля.
Как отвечать
Чтение из закрытого канала не блокируется и не паникует:
- буферизованный канал: сначала вычитываются все оставшиеся значения, закрытие этому не мешает;
- после опустошения: каждое чтение сразу возвращает нулевое значение типа, сколько бы раз вы ни читали;
- форма с флагом:
v, ok := <-chдаётok == false, если канал закрыт и пуст. Так отличают закрытие от честно отправленного0; for v := range ch: цикл сам завершается, когда канал закрыт и вычитан.
Паникуют другие операции:
- отправка в закрытый канал:
panic: send on closed channel; - повторное закрытие:
panic: close of closed channel; - закрытие nil-канала:
panic: close of nil channel.
Отдельный случай: чтение из nil-канала и запись в него блокируются навсегда. В select это полезно, так выключают отработавшую ветку.
Из этой семантики следует правило владения: закрывает канал только отправитель и только когда отправлять больше некому. Если писателей несколько, их ждут через sync.WaitGroup и закрывают канал в отдельной горутине после wg.Wait(). Остановить писателей со стороны получателя помогает context.Context, а не закрытие канала данных.
Где спотыкаются
Отвечают «да, будет паника», путая чтение с отправкой. Это самая частая ошибка, и она сразу снижает доверие к остальному ответу.
Держат закрытый канал в select без проверки ok. Ветка готова всегда, цикл крутится вхолостую и съедает ядро. Нужно проверить ok и присвоить переменной канала nil, чтобы select перестал её выбирать.
Закрывают канал со стороны читателя, чтобы остановить писателя. Писатель получает панику при следующей отправке. Флаг «уже закрыт» без синхронизации это гонка, recover маскирует ошибку дизайна, а для однократного закрытия есть sync.Once.