Буферизованные и небуферизованные каналы
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 61 из 107.
Каналы, которые мы использовали до сих пор, являются unbuffered. У них нет capacity для хранения значений. Операция отправки блокируется, пока другая goroutine не выполнит получение, и наоборот. Buffered channels добавляют внутреннее хранилище, позволяя операциям отправки завершаться без немедленного получателя.
Создайте буферизованный канал, передав ёмкость в make:
// Небуферизованный - ёмкость 0
unbuffered := make(chan int)
// Буферизованный - ёмкость 3
buffered := make(chan int, 3)С buffered channel отправка блокирует выполнение только когда буфер full, а получение — только когда буфер empty:
ch := make(chan string, 2)
ch <- "first" // не блокирует - в буфере есть место
ch <- "second" // не блокирует - в буфере есть место
// ch <- "third" заблокировало бы - буфер заполнен
fmt.Println(<-ch) // "first"
fmt.Println(<-ch) // "second"Используйте len(ch), чтобы проверить, сколько элементов сейчас находится в буфере, а cap(ch) — чтобы узнать общую capacity.
Когда использовать each: Небуферизованные каналы обеспечивают тесную синхронизацию: отправитель знает, что получатель получил значение. Buffered каналы разделяют моменты работы отправителя и получателя, что полезно, когда производители и потребители работают с разной скоростью. Однако Buffered каналы могут скрывать ошибки синхронизации, поэтому предпочитайте небуферизованные каналы, если у вас нет конкретной причины использовать буферизацию.
Задание
ЛегкоДавайте создадим пакетный процессор, демонстрирующий разницу между buffered и небуферизованными каналами. Вы создадите систему, в которой Producer отправляет items процессору, используя buffered каналы для разделения их временных интервалов и позволяя Producer работать с опережением.
Вы организуете код в двух файлах:
processor.go: Определите логику пакетной обработки с использованием каналов.Создайте структуру
Itemс полямиID(int) иValue(string).Implement две функции:
Producer(items []Item, out chan Item)— отправляет каждый item в выходной канал. После отправки каждого item выведите:Produced item [ID]. После отправки всех items выведите текущее number items, ожидающих в buffer, используяlen(out), в формате:Buffer has [count] items. Затем закройте канал.Consumer(in chan Item) []string— получает items из входного канала и собирает их в срез отформатированных строк. Для каждого полученного item строка должна иметь вид:Consumed: [ID] - [Value]. Верните срез, когда канал закрыт.
main.go: Настройте buffered канал и скоординируйте Producer и Consumer.Считайте capacity buffer, затем number items, после чего ID и value каждого item. Создайте buffered канал с указанной capacity. Сначала запустите Producer (не как goroutine), чтобы заполнить buffer, затем запустите Consumer для обработки всех items. Выведите каждый результат обработки на отдельной строке.
Будут предоставлены следующие входные данные:
- Строка 1: capacity buffer (целое число)
- Строка 2: number items (целое число)
- Следующие строки: для каждого item две строки — ID item (целое число), затем его value (строка)
Например, если даны:
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Обратите внимание: при capacity buffer, равной 3, Producer может отправить все 3 items без блокировки, и buffer показывает 3 ожидающих item до запуска Consumer. Если buffer меньше number items, Producer будет заблокирован в ожидании свободного места.
Попробуйте сами
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 (не как goroutine) для заполнения буфера
// 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Потренируйтесь самостоятельно: Онлайн-компилятор Go