Какая горутина должна закрывать канал и почему?

Ответ

Основное правило в Go: закрывать канал должна та горутина, которая в него пишет (отправитель). Никогда не закрывайте канал со стороны читающей горутины.

Почему именно отправитель?

  1. Предотвращение паники: Попытка отправить значение в уже закрытый канал вызывает панику (panic: send on closed channel). Отправитель точно знает, когда он закончил отправлять данные, и может безопасно закрыть канал. Читатель же никогда не знает, не собирается ли отправитель послать что-то еще.

  2. Сигнал о завершении: Закрытие канала — это сигнал для всех читающих горутин о том, что данных больше не будет. Это позволяет читателям корректно завершить свою работу.

Как читатель узнает, что канал закрыт?

Читающая горутина может использовать две формы для проверки состояния канала:

  1. Цикл for range: Этот цикл автоматически прекращается, когда канал закрывается и в нем не остается данных.

    for value := range ch {
        // Обработка value
    }
    // Цикл завершится, когда ch будет закрыт
  2. Проверка второго значения: При чтении из канала можно получить второй параметр ok (типа bool). Если ok равно false, это значит, что канал закрыт и данных в нем больше нет.

    value, ok := <-ch
    if !ok {
        // Канал закрыт, выходим
    }

Что делать, если писателей несколько?

Если в один канал пишут несколько горутин, закрывать его должен тот, кто координирует их работу. Обычно для этого используют sync.WaitGroup, чтобы дождаться завершения всех писателей, и только после этого закрыть канал в отдельной горутине.

func main() {
    var wg sync.WaitGroup
    ch := make(chan int)

    // Запускаем несколько писателей
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            ch <- id * 10
        }(i)
    }

    // Горутина, которая ждет завершения всех писателей и закрывает канал
    go func() {
        wg.Wait()
        close(ch)
    }()

    // Читатель
    for val := range ch {
        fmt.Println(val)
    }
}

Ответ 18+ 🔞

А, слушай, смотри, тут есть одно железобетонное правило в Go, которое в голове выжечь надо, как татуировку на подъезде: закрывать канал должна та горутина, которая в него пишет, то есть отправитель, ёпта! Никогда, слышишь, никогда не лезь закрывать канал с той стороны, где его читают. Это как зайти в чужой дом и выключить свет, пока хозяин ещё готовит ужин — пиздец наступит моментально.

А че так, а не иначе?

  1. Паника, блядь! Представь: ты читаешь, решил, что всё, и закрыл канал. А отправитель в это время уже замахнулся, чтобы кинуть туда ещё одно значение. БАМ! panic: send on closed channel. Программа ложится, как муха от дихлофоса. Отправитель же сам знает, когда он последний раз плюнул в эту трубу, ему и карты в руки — безопасно заткнуть её.

  2. Это сигнал, ёбта! Закрытый канал — это не просто дырка в никуда. Это официальное уведомление для всех, кто слушает: «Всё, ребята, передач больше не будет, расходимся». Читатели благодаря этому могут красиво и без истерик завершиться.

А как читателю понять, что всё кончилось?

Да элементарно, Ватсон! Есть два классических приёма:

  1. Цикл for range: Самый элегантный способ. Он работает, пока канал открыт, и сам, как умный, прекратится, когда канал закроют и все данные из него выпьют.

    for value := range ch {
        // Делаем что-то с value
    }
    // Сюда попадём, только когда ch закроют
  2. Проверка по-стариковски: Берёшь значение из канала и смотришь вторым глазом. Если второе значение — false, значит канал приказал долго жить, и новых плюшек не будет.

    value, ok := <-ch
    if !ok {
        // Всё, пипец, канал закрыт. Пора на выход.
    }

А если писателей, как говна за баней?

Ну, классика жанра! Если в одну трубу плюют несколько горутин, то закрывать её должен самый главный распорядитель. Обычно для этого берут sync.WaitGroup, чтобы дождаться, пока все писатели отстреляются, и только потом, в отдельной горутине, с чистой совестью захлопнуть канал.

func main() {
    var wg sync.WaitGroup
    ch := make(chan int)

    // Запускаем толпу писателей
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            ch <- id * 10 // Плюнул и свободен
        }(i)
    }

    // А это наш могильщик. Ждёт, пока все писатели умрут, и закрывает канал.
    go func() {
        wg.Wait()
        close(ch) // Вот теперь можно!
    }()

    // Читатель спокойно пьёт, пока есть что.
    for val := range ch {
        fmt.Println(val)
    }
}

Вот и вся магия. Запомни — писатель закрывает, читатель просто идёт домой, когда бар опустел. Не усложняй, и будет тебе счастье, а не паника.