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

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

Повторное закрытие уже закрытого канала вызывает панику (panic):

ch := make(chan int)
close(ch)
close(ch) // panic: close of closed channel

Подробное объяснение

Почему возникает panic?

  1. Нарушение контракта - канал может быть закрыт только один раз
  2. Защита от race conditions - предотвращение неопределенного поведения при конкурентном закрытии
  3. Явное указание на логическую ошибку в программе

Особенности поведения:

  1. Для всех типов каналов:

    • Буферизированные и небуферизированные каналы ведут себя одинаково
    • Не зависит от состояния канала (пуст/полон)
  2. Мгновенное возникновение:

    • Паника происходит сразу при вызове второй close()
    • Не зависит от наличия читателей/писателей
  3. Нельзя восстановить стандартными средствами:

    • Даже если закрытие происходит в той же горутине

Практические примеры

Пример 1: Простая паника

func main() {
    ch := make(chan int)
    close(ch)
    close(ch) // panic
}

Пример 2: Конкурентное закрытие

func main() {
    ch := make(chan int)

    go func() {
        close(ch)
    }()

    close(ch) // возможна panic при одновременном закрытии
}

Пример 3: С буферизированным каналом

func main() {
    ch := make(chan int, 3)
    ch <- 1; ch <- 2
    close(ch)
    close(ch) // panic, несмотря на данные в буфере
}

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

  1. Принцип единственного ответственного:

    • Четко определить, какая горутина закрывает канал
    • Документировать ответственность
  2. Использование sync.Once:

var once sync.Once
ch := make(chan int)

// В любой горутине:
once.Do(func() { close(ch) })
  1. Проверка состояния канала (косвенная):
select {
case <-ch:
    // канал уже закрыт
default:
    close(ch) // безопасное закрытие
}

Идиоматичные решения

  1. defer + однократное закрытие:
func worker(done chan struct{}) {
    defer close(done) // гарантированное однократное закрытие
    // работа...
}
  1. Использование оберток:
type SafeChan struct {
    ch    chan int
    once  sync.Once
    closed bool
}

func (sc *SafeChan) Close() {
    sc.once.Do(func() {
        close(sc.ch)
        sc.closed = true
    })
}

Резюмируем

  1. Повторное закрытие канала всегда вызывает panic
  2. Это поведение одинаково для всех типов каналов
  3. Паника возникает немедленно при попытке повторного закрытия
  4. Лучшая защита - четкое управление жизненным циклом канала
  5. Используйте sync.Once для безопасного закрытия в конкурентной среде

Правильное обращение с закрытием каналов - важный аспект написания надежного конкурентного кода на Go.