Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Устройство каналов в Go
Канал — это механизм синхронизации и передачи данных между горутинами. Каналы позволяют безопасно обмениваться информацией без явных блокировок и мьютексов.
Внутренняя структура канала
Внутренне канал представлен структурой с несколькими полями:
type hchan struct {
qcount uint // количество элементов в буфере
dataqsiz uint // размер буфера (capacity)
buf unsafe.Pointer // указатель на массив буфера
elemsize uint16 // размер одного элемента
closed uint32 // флаг закрытия канала
elemtype *_type // тип элементов канала
sendx uint // индекс в буфере для отправки
recvx uint // индекс в буфере для получения
recvq waitq // очередь горутин, ждущих приёма
sendq waitq // очередь горутин, ждущих отправки
lock mutex // мьютекс для синхронизации
}
Ненаправленные каналы (Bidirectional)
// Создание канала
ch := make(chan int) // без буфера (синхронный)
ch := make(chan int, 5) // с буфером на 5 элементов (асинхронный)
// Отправка
ch <- 42
// Получение
value := <-ch
Направленные каналы (Directional)
// Send-only канал
sendCh := make(chan<- int)
// Receive-only канал
recvCh := make(<-chan int)
// Конвертация между типами
var sendOnly chan<- int
var recvOnly <-chan int
var bidir chan int
sendOnly = bidir // OK: ненаправленный в send-only
recvOnly = bidir // OK: ненаправленный в recv-only
// bidir = sendOnly // ОШИБКА: не можно обратно
Механизм блокировки
Каналы без буфера (синхронные):
ch := make(chan int) // без буфера
go func() {
ch <- 42 // Заблокируется, пока кто-то не прочитает
}()
value := <-ch // Разблокирует отправку, получит 42
fmt.Println(value) // 42
Каналы с буфером (асинхронные):
ch := make(chan int, 2) // буфер на 2 элемента
ch <- 1 // OK, буфер не полон
ch <- 2 // OK, буфер не полон
// ch <- 3 // Заблокируется: буфер полон
v1 := <-ch // 1
v2 := <-ch // 2
// v3 := <-ch // Заблокируется: буфер пуст
Закрытие канала
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch) // Закрываем канал
// После закрытия:
// - Получение всегда успешно (пока буфер не пуст, потом вернёт нулевое значение)
// - Отправка вызовет панику
// - Проверка закрытия
value, ok := <-ch
if !ok {
fmt.Println("Канал закрыт")
}
Итерация по каналу
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for value := range ch {
fmt.Println(value) // 1, 2, 3
}
// После того как канал закрыт и буфер пуст, цикл завершается
Select для множественных каналов
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Received", msg1)
case msg2 := <-ch2:
fmt.Println("Received", msg2)
}
}
Практические паттерны
Паттерн Done сигнал:
func worker(done <-chan bool) {
for {
select {
case <-done:
return
default:
// работа
}
}
}
Пулинг результатов:
results := make(chan int, 10)
for i := 0; i < 10; i++ {
go func(n int) {
results <- n * 2
}(i)
}
for i := 0; i < 10; i++ {
fmt.Println(<-results)
}
Важные моменты
- Каналы потокобезопасны за счёт внутреннего мьютекса
- Нельзя отправлять в closed канал (паника)
- Приём из closed канала всегда успешен (нулевое значение)
- nil канал вызывает deadlock при операциях
- Закрывать канал должен отправитель, не получатель
Каналы — основной инструмент для безопасной конкурентности в Go.