#20.3 golang Закрытие каналов - close() range()

Закрывать каналы следует, когда отправитель данных завершил свою работу и больше не планирует отправлять значения.

Для закрытия канала используется вызов:

 close(имяКанала)

Если количество данных, которое должно попасть в канал получателю неизвестно, то закрытие канала служит сигналом для получателя, что можно переставать читать, например:

package main

import (
	"fmt"
)

// источник данных
func producer(ch chan<- int) {
	defer close(ch) // закрываем при завершении
	for i := 0; i < 5; i++ {
		ch <- i // пишем в канал
	}
}

// получатель
func consumer(ch <-chan int) {
	// цикл завершится, когда канал закроют
	for num := range ch {
		fmt.Println(num)
	}
}

func main() {
	ch := make(chan int)
	go producer(ch)
	consumer(ch)
}

-- тут мы:

Незакрытый канал для примера выше заблокирует горутину-получатель (consumer) и будет впустую использовать ресурсы вашей оперативной памяти.

Где правильно закрывать канал

Закрывай канал в реальном коде (если это не демонстрационный пример) следует только в горутине которая передает в него данные (producer), никогда не пытайся закрывать канал в горутине-получателе (consumer).

Иначе пишуший, код может продолжить передавать данные в канал и это вызовет ошибку (panic).

Пример кода, который иллюстрирует обработку паники в ситуации, когда канал закрыт, но в него пытаются записать значение:

package main

import (
	"fmt"
)

// Обработчик паник
func panicHandler() {
	// Проверим была ли ошибка
	if err := recover(); err != nil {
		// если была, то обработаем:
		fmt.Println(
			"Мы упали c сообщением: \n",
			err)
	}
}

func main() {
	defer panicHandler() // отложенная функция восстановления
	ch := make(chan int)
	close(ch)
	// пишем в уже закрытый канал
	ch <- 22 // Паника!
}

-- если запустим, то получим распечатку:

Мы упали c сообщением: 
 send on closed channel

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

Проверить закрыт ли канал (если вы не используете range, как в примере выше) в явной форме можно при попытке прочесть из него данные, операция чтения на самом деле всегда возвращает два значения:

  1. очередного значение из канала (или значение по умолчанию для данного типа данных, если канал закрыт)
  2. и вторым, опциональным для чтения параметром, bool-ответ (false при закрытом канале)

Посмотрим пример:

import (
	"fmt"
)

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

	// Закрываем канал до начала чтения
	close(ch)

	// пробуем считать значение
	// и признак открытости канала
	val, ok := <-ch
	fmt.Printf("Значение: %v, Канал открытыт?: %v\n",
		val,
		ok)
}