Как работает буферизированный канал в Go? Куда добавляются и откуда извлекаются элементы?

Ответ

Буферизированный канал в Go работает по принципу FIFO (First-In, First-Out), то есть как классическая очередь.

  • Отправка (ch <- value): Новое значение всегда добавляется в конец очереди (буфера).
  • Чтение (<- ch): Значение всегда извлекается из начала очереди (буфера).

Поведение:

  • Отправка: Если буфер не заполнен (len(ch) < cap(ch)), отправка происходит немедленно и без блокировки. Если буфер полон, отправляющая горутина блокируется до тех пор, пока другая горутина не прочитает значение из канала и не освободит место.
  • Чтение: Если буфер не пуст (len(ch) > 0), чтение происходит немедленно. Если буфер пуст, читающая горутина блокируется, пока в канал не будет отправлено новое значение.

Пример:

package main

import "fmt"

func main() {
    // Создаем канал с буфером на 3 элемента
    ch := make(chan int, 3)

    // Отправляем значения. Они добавляются в конец очереди.
    ch <- 1 // Канал: [1], len=1, cap=3
    ch <- 2 // Канал: [1, 2], len=2, cap=3
    ch <- 3 // Канал: [1, 2, 3], len=3, cap=3

    // Канал полон. Следующая отправка заблокировала бы горутину.
    // ch <- 4 // fatal error: all goroutines are asleep - deadlock!

    // Читаем значения. Они извлекаются из начала очереди.
    fmt.Println(<-ch) // Выведет: 1. Канал: [2, 3], len=2, cap=3
    fmt.Println(<-ch) // Выведет: 2. Канал: [3], len=1, cap=3

    // Можно снова отправлять, т.к. место освободилось
    ch <- 5 // Канал: [3, 5], len=2, cap=3

    fmt.Println(<-ch) // Выведет: 3
    fmt.Println(<-ch) // Выведет: 5
}