Буферизованные каналы
Ёмкость, закрытие каналов и range по каналу
«Буферизованные каналы» — бесплатный урок Go Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Go Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Go Academy содержит 4 уроков всего.
Что такое буферизованный канал
Буферизованный канал имеет ёмкость — отправка не блокируется, пока в буфере есть место. Канал работает как очередь:
package main
import "fmt"
func main() {
ch := make(chan int, 3) // capacity 3
ch <- 1 // does not block — buffer has room
ch <- 2 // does not block
ch <- 3 // does not block
// ch <- 4 // would block — buffer full
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
}Буферизованный канал как очередь
Буферизованные каналы реализуют очередь FIFO между goroutine:
package main
import ("fmt"; "sync")
func producer(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 1; i <= 5; i++ {
ch <- i
fmt.Printf("produced %d\n", i)
}
}
func main() {
ch := make(chan int, 2) // buffer 2 — producer runs ahead
var wg sync.WaitGroup
wg.Add(1)
go producer(ch, &wg)
wg.Wait()
close(ch)
for v := range ch { fmt.Printf("consumed %d\n", v) }
}len() и cap() для каналов
Проверяйте текущий размер и ёмкость буферизованного канала:
package main
import "fmt"
func main() {
ch := make(chan string, 5)
ch <- "a"
ch <- "b"
ch <- "c"
fmt.Println("cap:", cap(ch)) // 5
fmt.Println("len:", len(ch)) // 3 — items currently buffered
<-ch
fmt.Println("len:", len(ch)) // 2
}Разделение producer и consumer
Буфер отделяет скорость producer от скорости consumer. producer может опередить consumer на размер буфера, прежде чем заблокируется:
package main
import ("fmt"; "sync"; "time")
func produce(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
ch <- i
}
}
func consume(ch <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for v := range ch {
time.Sleep(10 * time.Millisecond) // slower consumer
fmt.Println("consumed:", v)
}
}
func main() {
ch := make(chan int, 3)
var wg sync.WaitGroup
wg.Add(2)
go produce(ch, &wg)
go consume(ch, &wg) // BUG: produce closes before consume sees all
// In practice, coordinate close carefully
fmt.Println("decoupled")
}Семафор с буферизованным каналом
Буферизованный канал ёмкостью N может ограничить количество одновременно выполняющихся goroutine значением N — это простой семафор:
package main
import ("fmt"; "sync"; "time")
func main() {
sem := make(chan struct{}, 3) // max 3 concurrent
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
i := i
go func() {
defer wg.Done()
sem <- struct{}{} // acquire
defer func() { <-sem }() // release
fmt.Printf("job %d running\n", i)
time.Sleep(50 * time.Millisecond)
}()
}
wg.Wait()
}Закрытие буферизованного канала
Закрытие буферизованного канала позволяет получателям извлечь оставшиеся буферизованные значения, прежде чем обнаружить закрытие:
package main
import "fmt"
func main() {
ch := make(chan int, 5)
for i := 1; i <= 3; i++ { ch <- i }
close(ch)
// Drain all buffered values after close:
for v := range ch {
fmt.Println(v) // 1, 2, 3
}
// range exits after channel closed and drained
}Буферизованный или небуферизованный канал: что выбрать
Руководство по выбору:
- Небуферизованный: гарантированная синхронная передача, строгий порядок, синхронизация через встречу
- Буферизованный: разделение скоростей producer и consumer, уменьшение блокировок, реализация семафоров, сбор N результатов
- По умолчанию выбирайте небуферизованный канал — добавляйте буфер только при обоснованном размере
- Слишком маленький буфер → блокировки всё равно возникают; слишком большой → скрывает ошибки
Сбор результатов с помощью буферизованного канала
Используйте буферизованный канал для сбора результатов параллельных goroutine без блокировки:
package main
import ("fmt"; "sync")
func square(n int) int { return n * n }
func main() {
nums := []int{1, 2, 3, 4, 5}
results := make(chan int, len(nums)) // buffer = goroutine count
var wg sync.WaitGroup
for _, n := range nums {
wg.Add(1)
n := n
go func() {
defer wg.Done()
results <- square(n)
}()
}
wg.Wait()
close(results)
for r := range results { fmt.Println(r) }
}Утечка goroutine при заполненном буфере
Если буфер заполнен и читателя нет, отправитель блокируется навсегда — это приводит к утечке goroutine:
package main
import ("fmt"; "time"; "runtime")
func main() {
ch := make(chan int, 2)
// Start goroutine that fills buffer and then blocks
go func() {
ch <- 1
ch <- 2
ch <- 3 // blocks: buffer full, no receiver
fmt.Println("never reached")
}()
time.Sleep(50 * time.Millisecond)
fmt.Println("goroutines:", runtime.NumGoroutine()) // 2 — leaked
}time.After как буферизованный канал
time.After возвращает канал <-chan time.Time с буфером размером 1. Среда выполнения отправляет в него значение после заданной длительности, после чего канал удаляется сборщиком мусора:
package main
import ("fmt"; "time")
func doWithTimeout(work func() string) (string, bool) {
result := make(chan string, 1)
go func() { result <- work() }()
select {
case r := <-result:
return r, true
case <-time.After(100 * time.Millisecond):
return "", false
}
}
func main() {
r, ok := doWithTimeout(func() string {
time.Sleep(50 * time.Millisecond)
return "done"
})
fmt.Println(r, ok)
}Краткая проверка
Что происходит, когда Вы отправляете значение в буферизованный канал, заполненный до максимальной ёмкости?
Повторение: буферизованные каналы
Краткое содержание:
make(chan T, N)создаёт буферизованный канал ёмкостью N- Отправка блокируется только при заполненном буфере; получение блокируется только при пустом буфере
len(ch)— текущее количество элементов;cap(ch)— ёмкость- Используйте их для разделения producer и consumer, сбора параллельных результатов и реализации семафоров
- Закрытие позволяет извлечь оставшиеся значения до EOF;
range chавтоматически обрабатывает этот случай
Часто задаваемые вопросы
Урок «Буферизованные каналы» бесплатный?
Да — полный текст урока «Буферизованные каналы» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Go Academy, подпишись на CoddyKit PRO. Курс Go Academy содержит 4 уроков всего.
Чему я научусь в уроке «Буферизованные каналы»?
Ёмкость, закрытие каналов и range по каналу Ты практикуешь Go Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Go Academy?
Предыдущий опыт не требуется. Go Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Буферизованные каналы»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Go Academy?
Да. Каждый урок Go Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Запуск горутин
- Небуферизованные каналы
- Буферизованные каналы
- Направление каналов и шаблоны конвейеров