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

Буферизованный канал - это канал с буфером, который позволяет отправлять данные без блокировки, пока буфер не заполнится, и принимать данные без блокировки, пока буфер не опустеет. При создании канала указывается его вместимость. Когда горутина отправляет значение в буферизованный канал, операция не блокируется, если в буфере есть свободное место. Аналогично, получение данных не блокируется, если буфер не пуст.

Синтаксис

gomake(chan Type, capacity)

Первый параметр определяет тип данных, которые будут передаваться через канал, второй - размер буфера. Для отправки данных используется оператор <-, а для приема - оператор <-.

Пример

Создадим буферизованный канал с вместимостью 2 и отправим в него два значения без блокировки:

gopackage main

import "fmt"

func main() {
  ch := make(chan string, 2)

  ch <- "first"
  ch <- "second"

  fmt.Println(<-ch)
  fmt.Println(<-ch)
}

Результат выполнения кода:

go"first"
"second"

Отправка двух значений не блокируется, так как буфер вмещает оба. После этого мы получаем их в порядке очереди.

Пример

Если буфер переполнен, отправка блокируется до тех пор, пока не появится свободное место. Аналогично, если буфер пуст, получение блокируется до появления данных. Рассмотрим пример с горутинами:

gopackage main

import (
  "fmt"
  "time"
)

func main() {
  ch := make(chan int, 2)

  go func() {
    time.Sleep(2 * time.Second)
    ch <- 42
    fmt.Println("Sent 42")
  }()

  fmt.Println("Waiting for value...")
  value := <-ch
  fmt.Println("Received:", value)
}

Результат выполнения кода:

go"Waiting for value..."
"Sent 42"
"Received: 42"

Горутина отправляет значение после задержки. Основная горутина блокируется при попытке получения данных, так как буфер пуст, и ожидает, пока значение не будет отправлено.

Пример

Использование буферизованного канала для ограничения количества одновременно выполняемых горутин. Это полезно для управления параллелизмом:

gopackage main

import (
  "fmt"
  "sync"
)

func main() {
  const maxWorkers = 2
  sem := make(chan struct{}, maxWorkers)
  var wg sync.WaitGroup

  for i := 1; i <= 5; i++ {
    wg.Add(1)
    go func(id int) {
      defer wg.Done()

      sem <- struct{}{}
      defer func() { <-sem }()

      fmt.Printf("Worker %d started\n", id)
      fmt.Printf("Worker %d finished\n", id)
    }(i)
  }

  wg.Wait()
}

Результат выполнения кода:

go"Worker 3 started"
"Worker 3 finished"
"Worker 5 started"
"Worker 5 finished"
"Worker 4 started"
"Worker 4 finished"
"Worker 1 started"
"Worker 1 finished"
"Worker 2 started"
"Worker 2 finished"

В этом примере канал sem используется как семафор. Он позволяет запускать не более двух горутин одновременно. Каждая горутина отправляет пустую структуру в канал перед началом работы и получает ее после завершения, освобождая место для следующей.

Смотрите также

  • канал chan,
    который описывает базовый синтаксис и принцип работы каналов
  • небуферизованный канал unbuffered-chan,
    который не имеет буфера и требует одновременной готовности отправителя и получателя
  • горутину goroutine,
    которая используется для параллельного выполнения кода с каналами
  • оператор выбора select,
    который позволяет ожидать операции с несколькими каналами