Как правильно передавать канал в горутину?

Ответ

Каналы в Go являются ссылочным типом. Это означает, что переменная типа chan хранит указатель на внутреннюю структуру канала в памяти. Когда вы передаете канал в функцию (в том числе при запуске горутины), вы по значению копируете этот указатель, а не сам канал. Обе копии указателя будут ссылаться на одну и ту же структуру данных, поэтому вы можете эффективно обмениваться данными.

Передача канала в горутину осуществляется так же, как передача любого другого аргумента.

Базовый пример:

// worker принимает канал, из которого будет только читать
func worker(id int, ch chan string) {
    // Цикл for range автоматически завершится, когда канал будет закрыт
    for msg := range ch {
        fmt.Printf("Worker %d received: %sn", id, msg)
    }
}

func main() {
    // 1. Создаем канал до запуска горутин
    messages := make(chan string)

    // 2. Запускаем горутину и передаем ей канал
    go worker(1, messages)

    // 3. Отправляем данные в канал из основной горутины
    messages <- "hello"
    messages <- "world"

    // 4. Закрываем канал, чтобы сообщить worker'у, что данных больше не будет
    close(messages)

    // Даем время worker'у на обработку (в реальном коде используется sync.WaitGroup)
    time.Sleep(time.Second)
}

Направленные каналы (Directional Channels)

Для повышения безопасности и ясности кода рекомендуется использовать направленные каналы в сигнатурах функций. Это позволяет на уровне компилятора указать, можно ли из канала только читать или только в него писать.

  • chan<- T — канал только для записи (отправки).
  • <-chan T — канал только для чтения (получения).

Пример с направленными каналами:

// pinger может только отправлять данные в канал
func pinger(pings chan<- string, msg string) {
    pings <- msg
}

// printer может только читать данные из канала
func printer(pings <-chan string) {
    msg := <-pings
    fmt.Println(msg)
}

func main() {
    pings := make(chan string, 1)
    go pinger(pings, "ping")
    go printer(pings)
    // ...
}

Ключевые моменты и лучшие практики:

  1. Создание: Канал всегда создается (make) до того, как он будет использован в горутинах.
  2. Закрытие: Канал должен закрывать отправитель. Попытка отправить данные в закрытый канал вызовет панику. Чтение из закрытого канала не вызывает панику, а возвращает нулевое значение для типа канала.
  3. Nil-каналы: Нулевое значение для канала — nil. Любая операция (отправка или получение) на nil-канале будет блокироваться навсегда. Это частый источник багов.