Что такое каналы в Go, для чего они нужны и какие бывают
Что проверяют
Умеете ли вы синхронизировать горутины передачей данных, а не общей памятью, и понимаете ли, откуда берутся дедлоки. Это ядро конкурентности в Go.
Как отвечать
Канал это типизированная труба, через которую горутины передают значения. Лозунг языка: «не общайтесь через общую память, разделяйте память через общение».
Каналов два вида:
- небуферизованный (
make(chan int)): отправка блокируется, пока кто-то не прочитает. Это точка встречи: обе стороны синхронизируются; - буферизованный (
make(chan int, 10)): отправка блокируется, только когда буфер полон. Даёт развязку по скорости между производителем и потребителем.
Ещё бывают направленные типы в сигнатурах: chan<- int только для записи, <-chan int только для чтения. Это не разные каналы, а ограничение, которое компилятор проверяет.
Основные приёмы:
ch := make(chan int)
go func() {
defer close(ch) // закрывает отправитель, и только он
for i := 0; i < 3; i++ { ch <- i }
}()
for v := range ch { fmt.Println(v) } // range сам завершится на closeselect ждёт сразу несколько каналов, а вместе с context даёт отмену по таймауту:
select {
case v := <-ch:
use(v)
case <-ctx.Done():
return ctx.Err()
}Где спотыкаются
Закрывают канал со стороны получателя. Закрывать должен отправитель: запись в закрытый канал это паника.
Читают из nil-канала или пишут в него: такая операция блокируется навсегда, и это классический дедлок.
Думают, что буфер спасает от блокировки. Он лишь откладывает её до заполнения.
Забывают, что чтение из закрытого канала не паникует, а сразу возвращает нулевое значение, и что отличить это помогает вторая переменная: v, ok := <-ch.