В какой момент горутина, навсегда заблокированная на чтении из канала без отправителя и закрытия, освобождает связанные с ней ресурсы?
Она не освобождает их автоматически: горутина остаётся заблокированной до отправки значения, закрытия канала или иного события, которое изменит управление. Такая ситуация образует утечку горутины и может удерживать связанные с ней объекты, включая стек и доступные через него данные.
Горутины в Go сделаны дешёвыми для запуска, а каналы — средством координации и передачи данных между ними. Это упрощает конкурентные программы, но не отменяет управление временем жизни: запущенная горутина не завершается сама только потому, что вызывающая функция больше не заинтересована в её результате.
Если горутина читает из канала, в который никто не отправляет значения и который никто не закрывает, планировщик переводит её в состояние ожидания. Она не потребляет процессор постоянно, но продолжает существовать.
При повторении такой ситуации число заблокированных горутин растёт. Это увеличивает потребление памяти, удерживает связанные ресурсы и может привести к исчерпанию лимитов или зависанию части сервиса.
Обычное чтение из канала блокируется, пока не появится значение или канал не будет закрыт. После закрытия чтение немедленно завершается: если буфер пуст, оно возвращает нулевое значение типа канала и признак закрытия false в двухзначной форме чтения.
Надёжный код должен иметь явный путь завершения: закрытие канала владельцем потока данных, сигнал отмены или контекст. Для долгоживущего воркера часто используют select, ожидающий либо данные, либо сигнал остановки.
Закрытие stop разблокирует ожидающую ветвь у всех горутин, которые читают из этого канала. Закрывать канал остановки должен его владелец; повторное закрытие или отправка в закрытый канал приводят к панике.
Присваивание nil переменной канала не разблокирует уже выполняющееся чтение: операция использует значение канала, с которым была начата. Чтение из nil-канала, напротив, блокируется навсегда, пока сама операция не будет прервана другим механизмом управления программой.
HTTP-обработчик запускает воркер, передаёт ему канал результатов и затем завершает запрос по тайм-ауту. Если воркер в этот момент ждёт новое значение, а отправитель результата уже ушёл, простой цикл чтения оставит горутину заблокированной.
Возможны три подхода. Удалять горутину принудительно нельзя: в Go нет безопасной операции принудительного убийства. Ожидание только закрытия канала надёжно лишь при строгом соблюдении протокола владельцем канала, а отдельный канал остановки требует явно передавать и корректно закрывать сигнал.
Практичный выбор — передать воркеру канал отмены или context.Context и проверять его в select вместе с рабочим каналом. При тайм-ауте обработчик инициирует отмену, воркер выходит из цикла, освобождает свои ресурсы, а число горутин остаётся ограниченным.
nil, чтобы остановить ожидающую горутину?Нет. Операция чтения уже получила конкретное значение канала и продолжает ждать на нём. Изменение переменной влияет только на будущие операции, поэтому для остановки нужен сигнал отмены, закрытие исходного канала или другой путь выхода.
Нет, пока горутина существует и ожидает завершения, она не становится обычным недостижимым объектом. Кроме того, её стек может содержать ссылки на данные, которые из-за этого тоже остаются достижимыми. Поэтому ожидание «пока сборщик мусора всё уберёт» не является способом устранения утечки горутин.
Да, если закрытие означает именно завершение потока данных и у канала есть один однозначный владелец. Получатели увидят оставшиеся буферизованные значения, затем признак закрытия; для небуферизованного канала они сразу получат завершение после закрытия. Если канал должен продолжать передавать данные, закрывать его для отмены нельзя — нужен отдельный канал остановки или контекст.