Ситуация: отправитель обращается к уже закрытому каналу. Какое наблюдаемое поведение следует ожидать?
Отправка в уже закрытый канал вызывает панику send on closed channel. Это отличается от получения из закрытого канала: получение допустимо и после исчерпания буфера возвращает нулевое значение с признаком закрытия.
Каналы в Go объединяют передачу данных и синхронизацию между горутинами. Операция close сообщает потребителям, что новых значений больше не будет, но не предназначена для остановки или блокировки отправителей.
Поэтому закрытие канала изменяет его состояние окончательно: после него разрешено получать оставшиеся значения, но отправка становится ошибкой. Такой контракт позволяет обнаруживать нарушение владения каналом непосредственно во время выполнения.
Если одна горутина закрывает канал, пока другая ещё может отправлять в него данные, отправитель завершится паникой. При нескольких отправителях опасно, когда каждый из них потенциально может вызвать close: первый закрывает канал, а последующие отправки или повторные закрытия приводят к ошибкам.
Особенно часто проблема возникает при завершении конвейера, отмене запроса и использовании select: проверка отдельного флага не гарантирует, что канал не будет закрыт между проверкой и отправкой.
Состояние закрытого канала проверяется самой операцией отправки. Если канал уже закрыт, значение не принимается и горутина немедленно паникует. Нельзя безопасно определить состояние канала отдельным вызовом, а затем полагаться на него: между проверкой и отправкой другая горутина может закрыть канал.
Правило владения обычно формулируют так: закрывает канал тот, кто управляет завершением всех отправителей. Если отправителей несколько, сначала нужно дождаться их завершения, а затем закрыть канал из отдельной координирующей горутины.
В примере close(ch) переводит канал в закрытое состояние, а последующая отправка вызывает панику. recover здесь только демонстрирует наблюдаемое поведение; в обычной программе им не следует маскировать ошибку владения каналом.
Закрывать канал необязательно, если потребителю не нужно узнавать о завершении потока значений: канал может оставаться открытым до освобождения всех ссылок. Для отмены работы обычно лучше использовать context.Context или отдельный сигнал завершения, а не закрывать канал, в который ещё могут писать независимые отправители.
Сервис запускает несколько обработчиков, которые отправляют результаты в общий канал. Изначально каждый обработчик закрывал канал после своей работы. Иногда первый завершившийся обработчик закрывал канал, после чего другой обработчик паниковал при отправке результата.
Рассматривались два варианта. Передача права закрытия одному обработчику была ненадёжной: он не знал, завершились ли остальные. Полный отказ от закрытия устранял панику, но потребитель не мог завершить цикл чтения по окончании результатов.
Выбранное решение — отдельная координирующая горутина, которая ждёт завершения всех отправителей и только затем закрывает канал. Это сохраняет сигнал окончания потока и гарантирует, что после закрытия новых отправок не будет.
1. Вопрос: Можно ли безопасно проверить, закрыт ли канал, перед отправкой?
Ответ: Нет, универсальной безопасной проверки для такого протокола нет. Даже если косвенно определить состояние канала, оно может измениться другой горутиной до фактической отправки. Надёжнее выстроить владение каналом так, чтобы отправители не могли конкурировать с операцией закрытия.
2. Вопрос: Что произойдёт при повторном закрытии канала?
Ответ: Повторный close также вызывает панику. Поэтому sync.Once может защитить именно от повторного закрытия, но не решает проблему отправителя, который конкурентно пишет в канал после первого закрытия. Сначала нужно гарантировать завершение всех отправителей, затем выполнять единственное закрытие.
3. Вопрос: Чем отправка в закрытый канал отличается от получения из закрытого канала?
Ответ: Отправка в закрытый канал всегда является ошибкой времени выполнения и вызывает панику. Получение из закрытого канала разрешено: сначала возвращаются значения, оставшиеся в буфере, затем нулевое значение типа и false при двухзначном чтении. Это позволяет потребителю корректно завершить обработку потока после его закрытия.