К чему приводит отправка значения в небуферизованный канал, если получатель завершил работу раньше отправителя?
Отправка блокируется, пока другой поток выполнения не примет значение. Если получатель завершил работу и нового получателя не появится, отправляющая горутина зависнет; это может привести к утечке горутины или взаимной блокировке.
Сам канал при этом не закрывается автоматически. Закрытие канала не является способом отменить уже ненужную отправку.
Горутины и каналы в Go предназначены для упрощения взаимодействия конкурентных операций. Канал небуферизованного типа одновременно передаёт значение и синхронизирует отправителя с получателем: передача считается завершённой только после встречи обеих сторон.
Такой подход опирается на идеи обмена сообщениями: вместо совместного изменения памяти операции явно передают данные. Это уменьшает потребность в ручной синхронизации, но требует управлять временем жизни операций и каналов.
Небуферизованный канал не хранит значения. Если получателя нет, операция отправки не может завершиться и приостанавливает текущую горутину.
Опасность возникает, когда получатель прекращает работу из-за тайм-аута, ошибки или отмены запроса, а отправитель об этом не узнаёт. В результате горутина может удерживать память и другие ресурсы, а ожидание нескольких таких горутин способно вызвать дедлок.
Отправку нужно связывать с механизмом отмены или с гарантированным жизненным циклом получателя. Обычно используют select: одна ветка пытается отправить значение, другая ожидает сигнал завершения.
После закрытия done ветка отмены становится готовой. Поскольку получателя у ch нет, отправка не может выиграть выбор, и горутина завершается. Канал done используется только как сигнал; закрывать его должен владелец, отвечающий за отмену.
Если отправка и отмена одновременно готовы, select выбирает одну из готовых веток псевдослучайно. Поэтому отмена не гарантирует, что значение никогда не будет отправлено: если получатель действительно готов, отправка может выиграть. Если требуется другая семантика, её нужно явно проектировать.
Буфер канала может временно уменьшить блокировки, но не решает проблему окончательно: буфер заполнится, если получатель прекратил работу. Размер буфера следует выбирать по модели нагрузки, а не использовать как замену отмене.
Нельзя безусловно закрывать канал отправителем, если существуют другие отправители: параллельная отправка в закрытый канал вызывает панику. Для отмены часто лучше применять отдельный сигнал или context.Context, а закрытие рабочего канала оставлять стороне, которая точно знает, что новых отправок больше не будет.
Сервис запускает горутину, которая получает результат от внешней системы и отправляет его в небуферизованный канал обработчику HTTP-запроса. Клиент разрывает соединение, обработчик завершается, но фоновая горутина продолжает ждать при отправке результата.
Рассматривались три варианта:
Выбран третий вариант: отправка выполняется через select с веткой отмены контекста. Результат либо передаётся активному обработчику, либо отбрасывается после отмены запроса; зависших горутин не остаётся.
Дополнительный вопрос: Чем в этой ситуации отличается небуферизованный канал от буферизованного?
Ответ: Небуферизованный канал требует одновременной готовности отправителя и получателя. Буферизованный канал позволяет отправителю завершить операцию, пока в буфере есть свободное место, поэтому получатель может принять значение позже.
Однако после заполнения буфера отправка также блокируется. Буфер изменяет момент блокировки, но не устраняет необходимость в отмене и корректном завершении потребителя.
Дополнительный вопрос: Что произойдёт, если вместо сигнала отмены закрыть канал, в который выполняется отправка?
Ответ: Отправка в закрытый канал вызывает панику. Закрытый канал разрешает безопасно получать уже накопленные значения, но не принимает новые значения.
Поэтому канал данных нельзя закрывать как универсальный сигнал отмены, особенно если отправителей несколько. Для уведомления об остановке используют отдельный канал-сигнал или контекст, а канал данных закрывают только при гарантии отсутствия будущих отправок.
Дополнительный вопрос: Почему наличие ветки отмены в select всё ещё может не гарантировать немедленное прекращение работы?
Ответ: Select реагирует только на операции, перечисленные в нём. Если горутина после успешной отправки выполняет долгую блокирующую операцию, она не узнает об отмене, пока эта операция не поддерживает отдельную отмену.
Кроме того, если одновременно готовы отправка и отмена, выбор между ними недетерминирован. Надёжное завершение требует проверять отмену на всех потенциально блокирующих этапах и корректно прекращать связанные операции, а не ограничиваться одним select.