Что будет, если писать в закрытый канал?go-83

Основное поведение

Попытка записи в закрытый канал вызывает панику (panic) в runtime. Это одно из ключевых отличий от чтения из закрытого канала, которое является безопасной операцией.

ch := make(chan int)
close(ch)
ch <- 42 // panic: send on closed channel

Детали возникновения паники

  1. Тип паники: runtime error: send on closed channel
  2. Момент возникновения: Непосредственно при попытке выполнения операции записи
  3. Критичность: Если паника не обработана, она приведет к аварийному завершению программы

Примеры для разных типов каналов

1. Небуферизованный канал

ch := make(chan int)
close(ch)
ch <- 1 // Всегда паника

2. Буферизованный канал

ch := make(chan int, 1)
close(ch)
ch <- 1 // Паника, даже если есть место в буфере

Почему так спроектировано?

  1. Предотвращение скрытых ошибок: Явная паника лучше неявного молчаливого пропуска данных
  2. Контроль за состоянием: Четкое разделение между активным и закрытым состоянием
  3. Безопасность: Избегание состояний гонки (race conditions) при конкурентном доступе

Как безопасно работать с каналами

1. Проверка на возможность записи

select {
case ch <- data:
    // Успешная запись
default:
    // Канал заблокирован (полон или закрыт)
}

2. Использование sync.Once для закрытия

var once sync.Once
closeCh := make(chan struct{})

// Где-то в коде:
once.Do(func() { close(closeCh) }) // Гарантирует однократное закрытие

3. Отслеживание состояния канала

var (
    dataCh   = make(chan int)
    isClosed bool
    mu       sync.Mutex
)

func safeSend(v int) bool {
    mu.Lock()
    defer mu.Unlock()
    if isClosed {
        return false
    }
    select {
    case dataCh <- v:
        return true
    default:
        return false
    }
}

Обработка паники

Хотя лучше избегать записи в закрытый канал, можно обработать панику:

func safeChannelSend(ch chan<- int, value int) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("channel send panic: %v", r)
        }
    }()
    ch <- value
    return nil
}

Разница между чтением и записью

Операция Закрытый канал Незакрытый канал
Чтение (<-ch) Возвращает zero-value Блокирует/возвращает данные
Запись (ch<-) Паника Блокирует/передает данные

Практические рекомендации

  1. Закрывайте каналы только отправителем
  2. Используйте defer для закрытия, если это уместно
  3. Избегайте дублирования закрытия канала
  4. Документируйте, кто отвечает за закрытие канала

Резюмируем

запись в закрытый канал в Go всегда вызывает панику, независимо от типа канала (буферизованный/небуферизованный). Это осознанное решение языка для предотвращения скрытых ошибок. Всегда проектируйте свою программу так, чтобы избегать подобных ситуаций, используя правильные шаблоны работы с каналами и синхронизацию.