Ответ
Каналы в 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)
// ...
}
Ключевые моменты и лучшие практики:
- Создание: Канал всегда создается (
make
) до того, как он будет использован в горутинах. - Закрытие: Канал должен закрывать отправитель. Попытка отправить данные в закрытый канал вызовет панику. Чтение из закрытого канала не вызывает панику, а возвращает нулевое значение для типа канала.
- Nil-каналы: Нулевое значение для канала —
nil
. Любая операция (отправка или получение) наnil
-канале будет блокироваться навсегда. Это частый источник багов.