Буферизованные и небуферизованные каналы
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 61 из 107.
Каналы, которые мы использовали до сих пор, являются небуферизованными — они не имеют емкости для хранения значений. Операция отправки блокируется до тех пор, пока другая горутина не выполнит получение, и наоборот. Буферизованные каналы добавляют внутреннее хранилище, позволяя операциям отправки завершаться без немедленного получателя.
Создайте буферизованный канал, передав емкость в make:
// Небуферизованный — емкость 0
unbuffered := make(chan int)
// Буферизованный — емкость 3
buffered := make(chan int, 3)С буферизованным каналом операции отправки блокируются только тогда, когда буфер заполнен, а операции получения — только когда буфер пуст:
ch := make(chan string, 2)
ch <- "first" // не блокирует — в буфере есть место
ch <- "second" // не блокирует — в буфере есть место
// ch <- "third" заблокирует — буфер заполнен
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Используйте len(ch), чтобы проверить, сколько элементов в данный момент находится в буфере, и cap(ch) для получения общей емкости.
Когда использовать каждый из них: Небуферизованные каналы обеспечивают строгую синхронизацию — отправитель знает, что получатель получил значение. Буферизованные каналы разделяют время работы отправителя и получателя, что полезно, когда производители и потребители работают с разной скоростью. Однако буферизованные каналы могут скрывать ошибки синхронизации, поэтому отдавайте предпочтение небуферизованным, если у вас нет конкретной причины для использования буферизации.
Задание
ЛегкоДавайте создадим пакетный процессор, который демонстрирует разницу между буферизованными и небуферизованными каналами. Вы создадите систему, в которой производитель отправляет элементы процессору, используя буферизованные каналы для разделения времени их работы и предоставления производителю возможности работать на опережение.
Вы организуете свой код в двух файлах:
processor.go: Определите логику пакетной обработки с использованием каналов.Создайте структуру
Itemс полямиID(int) иValue(string).Реализуйте две функции:
Producer(items []Item, out chan Item)— отправляет каждый элемент в выходной канал. После отправки каждого элемента выведите:Produced item [ID]. После того как все элементы будут отправлены, выведите текущее количество элементов, ожидающих в буфере, используяlen(out)в формате:Buffer has [count] items. Затем закройте канал.Consumer(in chan Item) []string— получает элементы из входного канала и собирает их в срез отформатированных строк. Для каждого полученного элемента строка должна быть:Consumed: [ID] - [Value]. Верните срез, когда канал будет закрыт.
main.go: Настройте буферизованный канал и скоординируйте работу производителя и потребителя.Считайте емкость буфера, затем количество элементов, а затем ID и значение каждого элемента. Создайте буферизованный канал с указанной емкостью. Сначала запустите Producer (не как горутину), чтобы заполнить буфер, затем запустите Consumer для обработки всех элементов. Выведите каждый результат потребления на отдельной строке.
Будут предоставлены следующие входные данные:
- Строка 1: Емкость буфера (целое число)
- Строка 2: Количество элементов (целое число)
- Следующие строки: Для каждого элемента две строки — ID элемента (целое число), затем его значение (строка)
Например, при вводе:
3
3
1
apple
2
banana
3
cherryВаш вывод должен быть:
Produced item 1
Produced item 2
Produced item 3
Buffer has 3 items
Consumed: 1 - apple
Consumed: 2 - banana
Consumed: 3 - cherryОбратите внимание, что при емкости буфера 3 производитель может отправить все 3 элемента без блокировки, и буфер показывает 3 ожидающих элемента до начала работы потребителя. Если бы буфер был меньше количества элементов, производитель заблокировался бы в ожидании свободного места.
Шпаргалка
Каналы в Go могут быть небуферизованными (емкость 0) или буферизованными (с внутренним хранилищем).
Создавайте каналы с помощью make:
// Небуферизованный - емкость 0
unbuffered := make(chan int)
// Буферизованный - емкость 3
buffered := make(chan int, 3)Поведение буферизованного канала:
- Отправка блокируется только тогда, когда буфер заполнен
- Получение блокируется только тогда, когда буфер пуст
ch := make(chan string, 2)
ch <- "first" // не блокируется - в буфере есть место
ch <- "second" // не блокируется - в буфере есть место
// ch <- "third" заблокирует - буфер заполнен
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Проверка состояния буфера:
len(ch)— количество элементов, находящихся в данный момент в буфереcap(ch)— общая емкость буфера
Когда использовать:
- Небуферизованные: Строгая синхронизация — отправитель знает, что получатель принял значение
- Буферизованные: Разделение времени работы отправителя и получателя, когда они работают с разной скоростью
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Считать емкость буфера
line, _ := reader.ReadString('\n')
bufferCapacity, _ := strconv.Atoi(strings.TrimSpace(line))
// Считать количество элементов
line, _ = reader.ReadString('\n')
numItems, _ := strconv.Atoi(strings.TrimSpace(line))
// Считать ID и значение каждого элемента
items := make([]Item, numItems)
for i := 0; i < numItems; i++ {
line, _ = reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(line))
line, _ = reader.ReadString('\n')
value := strings.TrimSpace(line)
items[i] = Item{ID: id, Value: value}
}
// TODO: Создать буферизованный канал с указанной емкостью
// TODO: Запустить Producer (не как горутину), чтобы заполнить буфер
// TODO: Запустить Consumer для обработки всех элементов
// TODO: Вывести каждый обработанный результат на отдельной строке
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool