История вопроса:
Каналы (chan) — ключевой инструмент для обмена данными между горутинами и синхронизации конкурентных процессов, что однозначно выделяет Go среди других языков. Они предназначены для организации потокобезопасной передачи данных.
Проблема:
Без правильного понимания устройства каналов разработчики часто сталкиваются с deadlock-ами, неожиданными блокировками, неочевидными ошибками при закрытии канала и потерей данных (race condition).
Решение:
Необходимо чётко понимать как объявлять, использовать, закрывать каналы. Решать, когда использовать буферизованные/небуферизованные каналы, и следить за тем, кто и когда должен закрывать канал, чтобы избежать "всё зависло".
Пример кода:
func producer(ch chan<- int) { // только отправка for i := 0; i < 5; i++ { ch <- i } close(ch) } func consumer(ch <-chan int) { // только чтение for v := range ch { fmt.Println(v) } } func main() { ch := make(chan int) go producer(ch) consumer(ch) }
Ключевые особенности:
Что произойдёт при попытке записать в закрытый канал?
Возникнет panic. Достаточно частая ошибка — закрывать канал на стороне читателя или позволять записывать в канал после его закрытия.
Можно ли безопасно узнать, закрыт ли канал?
Нет, прямой проверки нет. Нужно только правильно проектировать контроль за жизнью канала, либо использовать второй параметр при чтении (v, ok := <-ch), который станет false когда канал закрыт.
Является ли канал потокобезопасным сам по себе?
Канал — потокобезопасен для передачи данных между горутинами. Но если несколько горутин пишут в один канал, без координации это чревато проблемами (возможно преждевременное закрытие).
Проект с несколькими продюсерами и одним консюмером: канал закрывали каждый продюсер, итог — panic в рантайме при попытке двойного закрытия, данные терялись.
Плюсы:
Минусы:
Использовали только один поток для закрытия канала, а продюсеры через отдельный WaitGroup сигнализировали о завершении работы. Канал служил исключительно для передачи результатов.
Плюсы:
Минусы: