0Pricing
Go Academy · Урок

Буферизованные каналы

Ёмкость, закрытие каналов и 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 — локальная установка не требуется.

Все уроки этого курса

  1. Запуск горутин
  2. Небуферизованные каналы
  3. Буферизованные каналы
  4. Направление каналов и шаблоны конвейеров
← Назад к Go Academy