Опишите поведение обычной отправки в nil-канал и чтения из него вне конструкции select.
И отправка в nil-канал, и чтение из него блокируются навсегда: отправка не может найти получателя, а чтение — отправителя. Операция не завершается значением, ошибкой или паникой.
Это отличается от операции с закрытым каналом: чтение из закрытого канала завершается, а отправка в закрытый канал вызывает панику.
Каналы в Go имеют нулевое значение nil, поэтому переменная типа канала может существовать до фактической инициализации. Такое поведение позволяет безопасно представить отсутствие канала без специального значения-заменителя.
Блокировка nil-канала также согласуется с моделью select: nil-канал может динамически отключить соответствующую ветвь. Однако вне select это обычно означает ошибку жизненного цикла или конфигурации канала.
Если горутина выполняет операцию с nil-каналом, она не достигнет следующих инструкций. При отсутствии другой горутины, способной завершить нужную логику, это приводит к зависшей горутине, утечке ресурсов и возможной остановке всей цепочки обработки.
Особенно опасна ситуация, когда канал должен был быть создан конструктором, но вместо этого остался нулевым. Программа может выглядеть исправной, но зависнуть только при определённом пути выполнения.
Значение nil означает, что канал не связан ни с каким внутренним объектом передачи. Поэтому у отправки нет получателя, а у чтения нет источника; обе операции блокируются без установленного тайм-аута.
Операция с nil-каналом не изменяет его состояние. Нельзя «разблокировать» такую отправку закрытием канала: закрытие nil-канала само вызывает панику. Нужно присвоить переменной настоящий канал, причём присваивание должно быть корректно синхронизировано, если переменная доступна нескольким горутинам.
В select case с nil-каналом считается недоступным. Это полезно для временного отключения направления обмена, но прямые операции с nil-каналом следует рассматривать как зависание, а не как способ ожидания.
Минимальная иллюстрация:
Обе операции с ch недоступны, поэтому срабатывает тайм-аут. Если выполнить ch <- 1 или <-ch обычной инструкцией, текущая горутина заблокируется без ограничения по времени.
В сервисе канал результатов создавался только при включённом режиме параллельной обработки. В одном из режимов воркер всё равно пытался отправить результат в этот канал, который оставался nil. Воркер зависал, а вызывающий код бесконечно ждал его завершения.
Рассматривались три варианта:
Выбрали обязательное создание канала в конструкторе и проверку конфигурации на старте. В результате зависание превратилось в немедленную ошибку запуска, а тайм-ауты оставили только для действительно внешних операций.
Нет. Вызов close для nil-канала вызывает панику. Закрытие обычного канала завершает ожидающие чтения, но nil-канал не является созданным каналом и не имеет состояния, которое можно закрыть.
Нет. Операция использует значение канала, полученное в момент её выполнения. Если горутина уже начала отправку или чтение через nil-значение, последующее присваивание другой переменной настоящего канала её не переключит. Кроме того, совместное изменение переменной канала без синхронизации может создать гонку данных.
Обычный канал может разблокировать операцию: получатель завершит отправку в полный канал, отправитель — чтение из пустого канала, а закрытие канала завершит чтение. Для nil-канала нет ни буфера, ни участников передачи, ни допустимого закрытия, поэтому операция сама по себе не завершится никогда. Прервать ожидание можно только архитектурно — например, использовать select с каналом отмены или не выполнять операцию до инициализации канала.