Небуферизованные каналы
Синхронная передача данных между горутинами
«Небуферизованные каналы» — бесплатный урок Go Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Go Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Go Academy содержит 4 уроков всего.
Что такое канал
Канал — это типизированный способ обмена данными между goroutine. Каналы следуют принципу Go: Не обменивайтесь данными через общую память; используйте общую память через обмен данными.
package main
import "fmt"
func main() {
ch := make(chan int) // unbuffered int channel
go func() {
ch <- 42 // send: blocks until someone receives
}()
v := <-ch // receive: blocks until someone sends
fmt.Println(v) // 42
}Семантика небуферизованного канала
Небуферизованный канал не имеет ёмкости. Отправка блокируется, пока не будет готов получатель, а получение блокируется, пока отправитель не отправит значение. Это обеспечивает гарантированную синхронную передачу:
package main
import "fmt"
func double(in <-chan int, out chan<- int) {
for v := range in {
out <- v * 2
}
close(out)
}
func main() {
in := make(chan int)
out := make(chan int)
go double(in, out)
go func() { in <- 3; in <- 5; close(in) }()
for v := range out { fmt.Println(v) }
// 6
// 10
}Канал как средство синхронизации
Небуферизованные каналы синхронизируют goroutine — используйте их как сигнал о завершении работы:
package main
import "fmt"
func compute(result chan<- int) {
sum := 0
for i := 1; i <= 100; i++ { sum += i }
result <- sum
}
func main() {
ch := make(chan int)
go compute(ch)
fmt.Println("Result:", <-ch) // blocks until compute sends
}Закрытие канала
Отправитель закрывает канал с помощью close(ch), чтобы сообщить, что значения больше отправляться не будут. Получатели обнаруживают закрытие через форму с двумя значениями:
package main
import "fmt"
func generate(ch chan<- int, max int) {
for i := 0; i < max; i++ {
ch <- i
}
close(ch) // signal done
}
func main() {
ch := make(chan int)
go generate(ch, 5)
for v := range ch { // range detects close automatically
fmt.Println(v)
}
fmt.Println("channel closed")
}Получение двух значений
Получение двух значений v, ok := <-ch сообщает, открыт ли ещё канал:
package main
import "fmt"
func main() {
ch := make(chan string, 1)
ch <- "hello"
close(ch)
v1, ok1 := <-ch
fmt.Println(v1, ok1) // hello true
v2, ok2 := <-ch
fmt.Println(v2, ok2) // "" false — channel closed
}Отправка в закрытый канал вызывает панику
Отправка в закрытый канал вызывает панику. Только отправитель должен закрывать канал — получатель никогда не должен этого делать:
package main
import "fmt"
func main() {
ch := make(chan int)
close(ch)
// Receiving from closed channel returns zero value
v, ok := <-ch
fmt.Println(v, ok) // 0 false
// Sending to closed channel panics:
defer func() { recover() }()
ch <- 1 // panic: send on closed channel
}Однонаправленные каналы
Для ясности и безопасности ограничивайте каналы в сигнатурах функций режимом только отправки (chan<- T) или только получения (<-chan T):
package main
import "fmt"
func producer(out chan<- int) { // send-only
for i := 0; i < 3; i++ { out <- i }
close(out)
}
func consumer(in <-chan int) { // receive-only
for v := range in { fmt.Println(v) }
}
func main() {
ch := make(chan int)
go producer(ch)
consumer(ch)
}Пинг-понг между goroutine через каналы
Две goroutine передают сообщение друг другу через каналы — классическая демонстрация синхронизации:
package main
import "fmt"
func ping(ping, pong chan struct{}) {
for i := 0; i < 3; i++ {
<-ping
fmt.Println("ping")
pong <- struct{}{}
}
}
func main() {
ping := make(chan struct{})
pong := make(chan struct{})
go func() { for { <-pong; fmt.Println("pong"); ping <- struct{}{} } }()
go func() { for i := 0; i < 3; i++ { ping <- struct{}{}; <-pong } }()
// Simplified version:
fmt.Println("channels enable structured communication")
}Каналы nil блокируют навсегда
Получение из канала nil или отправка в него блокируется навсегда. Иногда это намеренно используется в операторах select:
package main
import "fmt"
func main() {
var ch chan int // nil channel
fmt.Println(ch == nil) // true
// select with nil channel — that case is never selected
active := make(chan int, 1)
active <- 42
select {
case v := <-ch: // never selected (nil)
fmt.Println(v)
case v := <-active: // selected
fmt.Println("got:", v)
}
}Когда использовать небуферизованные каналы
Используйте небуферизованные каналы, когда:
- Вам нужна гарантированная синхронная передача
- goroutine должна подтвердить получение, прежде чем отправитель продолжит работу
- Вы реализуете семафоры или обратное давление
- Вы создаёте pipeline, в котором каждый этап должен обрабатывать по одному элементу за раз
Краткая проверка
Что происходит, когда Вы отправляете значение в небуферизованный канал, а готового получателя нет?
Повторение: небуферизованные каналы
Краткое содержание:
- Создавайте с помощью
make(chan T)— нулевая ёмкость - Отправка блокируется до готовности получателя; получение блокируется до отправки от отправителя
- Только отправитель должен закрывать канал
range chперебирает значения до закрытия канала- Получение двух значений:
v, ok := <-ch - Для ясности используйте однонаправленные типы:
chan<-и<-chan
Часто задаваемые вопросы
Урок «Небуферизованные каналы» бесплатный?
Да — полный текст урока «Небуферизованные каналы» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Go Academy, подпишись на CoddyKit PRO. Курс Go Academy содержит 4 уроков всего.
Чему я научусь в уроке «Небуферизованные каналы»?
Синхронная передача данных между горутинами Ты практикуешь Go Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Go Academy?
Предыдущий опыт не требуется. Go Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Небуферизованные каналы»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Go Academy?
Да. Каждый урок Go Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Запуск горутин
- Небуферизованные каналы
- Буферизованные каналы
- Направление каналов и шаблоны конвейеров