15 вопросов
Каналы - основной механизм коммуникации между горутинами в Go. Буферизованные и небуферизованные, однонаправленные, select, закрытие каналов и типичные паттерны использования.
make(chan int, 5)? 🟢 Лёгкий
▶
make(chan int, 5) создаёт буферизованный канал с ёмкостью 5. Тип - chan int (двунаправленный). Каналы - ссылочные типы, make возвращает сам канал, не указатель на него.
Подробнее →select без default блокируется, пока один из каналов не будет готов. С default выполняет default немедленно, если ни один канал не готов. Используется для неблокирующего чтения/записи и try-send паттерна.
Подробнее →Паттерн select + time.After: если данные из канала не приходят за время d, срабатывает ветка таймаута. Для длительных циклов лучше использовать context.WithTimeout.
Подробнее →Чтение из закрытого канала не блокирует - возвращается zero value типа элемента. Для проверки: v, ok := <-ch, где ok = false означает "канал закрыт". Цикл for v := range ch автоматически завершается при закрытии.
Подробнее →Небуферизованный канал (make(chan int)) синхронный: отправитель блокируется, пока получатель не прочитает. Если получателя нет - горутина навсегда заблокирована. Если это единственная горутина - deadlock.
Подробнее →select в Go? 🟡 Средний
▶
select позволяет ждать на нескольких каналах одновременно. Выполняет первый готовый case. Если несколько готовы - выбирает случайно. С default - не блокирует.
Подробнее →Отправка в закрытый канал вызывает panic: send on closed channel. Закрывать канал должен только отправитель, и только когда больше данных не будет. Получатели не должны закрывать каналы.
Подробнее →chan<- int? 🟡 Средний
▶
chan<- int - канал, в который можно только отправлять. <-chan int - только получать. Используются в сигнатурах функций для ограничения доступа. Двунаправленный канал автоматически приводится к однонаправленному.
Подробнее →for v := range ch читает из канала, пока он не закрыт. При закрытии цикл завершается. Самый чистый способ итерации по каналу. Не забудьте закрыть канал, иначе цикл заблокируется навсегда.
Подробнее →close(ch) при повторном вызове вызывает panic: close of closed channel. Канал можно закрыть только один раз. Проверки "закрыт ли канал" без чтения из него нет.
Подробнее →len(ch) для буферизованного канала? 🟡 Средний
▶
len(ch) возвращает число элементов, находящихся в буфере канала в данный момент. cap(ch) возвращает ёмкость буфера. Для небуферизованного канала len всегда 0. Важно: к моменту использования результата другая горутина может изменить содержимое буфера.
Подробнее →select {} (пустой select без case)? 🟡 Средний
▶
Пустой select без case блокирует горутину навсегда - нет ни одного case, который мог бы сработать. Используется в main, когда вся работа идёт в других горутинах и нужно предотвратить завершение программы. Аналог бесконечного цикла, но без потребления CPU.
Подробнее →<-chan int обратно к chan int? 🟡 Средний
▶
chan int можно привести к <-chan int (только чтение) или chan<- int (только запись), но обратное невозможно. Это обеспечивает безопасность: получатель не сможет случайно закрыть или записать в канал. Используйте в сигнатурах функций для ограничения доступа.
Подробнее →Мьютекс: защита разделяемого состояния (map, counter) - проще и производительнее. Каналы: координация и передача данных между горутинами (pipeline, fan-out). Мьютекс для простого shared state вполне идиоматичен в Go, несмотря на девиз 'share by communicating'.
Подробнее →var ch chan int; <-ch? 🔴 Сложный
▶
Чтение из nil-канала блокирует горутину навсегда. Если это единственная горутина - runtime обнаружит deadlock. Отправка в nil-канал тоже блокирует навсегда. Это поведение используется в select для динамического отключения кейсов.
Подробнее →