Оператор select
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 62 из 107.
Оператор select позволяет горутине ожидать выполнения нескольких операций с каналами одновременно. Он похож на оператор switch, но каждый вариант (case) связан с отправкой или получением данных из канала. Когда готовы сразу несколько каналов, select выбирает один из них случайным образом.
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() { ch1 <- "from channel 1" }()
go func() { ch2 <- "from channel 2" }()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}Без select, получение из ch1 будет блокироваться до тех пор, пока не поступят данные, что потенциально может привести к пропуску данных в ch2. С помощью select, обрабатывается тот канал, который первым получит данные.
Секция default делает select неблокирующим — она выполняется немедленно, если ни один из каналов не готов:
select {
case msg := <-messages:
fmt.Println("Received:", msg)
default:
fmt.Println("No message available")
}Распространенный паттерн объединяет select с таймаутами, используя time.After, который возвращает канал, получающий значение по истечении указанного времени:
select {
case result := <-ch:
fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
fmt.Println("Timeout!")
}Это предотвращает бесконечное ожидание вашей программы в случаях, когда операция в канале может никогда не завершиться.
Задание
ЛегкоДавайте создадим монитор сервисов, который использует оператор select для обработки нескольких одновременных источников данных. Ваш монитор будет одновременно прослушивать различные каналы сервисов и соответствующим образом реагировать при поступлении данных или при отсутствии обновлений.
Вы организуете свой код в двух файлах:
monitor.go: Определите логику мониторинга сервисов с обработкой каналов на основе select.Создайте структуру
ServiceUpdateс полямиName(string) иStatus(string).Реализуйте функцию
Monitor, которая принимает три канала:primary chan ServiceUpdate,backup chan ServiceUpdateиdone chan bool. Эта функция должна использоватьselectдля одновременного прослушивания всех трех каналов:- При получении данных из
primary: вернуть отформатированную строкуPrimary: [Name] is [Status] - При получении данных из
backup: вернуть отформатированную строкуBackup: [Name] is [Status] - При получении данных из
done: вернутьMonitoring stopped
Реализуйте функцию
CheckStatus, которая принимает один каналupdates chan ServiceUpdateи используетselectс блокомdefaultдля выполнения неблокирующей проверки:- Если обновление доступно: вернуть
Update available: [Name] - Если обновление недоступно (блок default): вернуть
No updates pending
- При получении данных из
main.go: Настройте каналы и продемонстрируйте как блокирующее, так и неблокирующее поведение select.Считайте из входных данных режим (mode), который определяет сценарий запуска:
primary,backup,stopилиcheck.Для режимов
primary,backupиstop: также считайте имя сервиса и статус. Создайте три канала, запустите goroutine, которая отправляет данные в соответствующий канал в зависимости от режима, затем вызовитеMonitorи выведите результат.Для режима
check: считайте имя сервиса и статус, а также флаг (sendилиnosend), указывающий, нужно ли отправлять данные перед проверкой. Создайте буферизованный канал емкостью 1. Если флаг равенsend, отправьте обновление в канал перед вызовомCheckStatus. Выведите результатCheckStatus.
Будут предоставлены следующие входные данные:
- Строка 1: Режим (
primary,backup,stopилиcheck) - Строка 2: Имя сервиса
- Строка 3: Статус сервиса
- Строка 4 (только для режима
check):sendилиnosend
Например, при вводе:
primary
Database
healthyВаш вывод должен быть:
Primary: Database is healthyИ при вводе:
check
Cache
active
nosendВаш вывод должен быть:
No updates pendingШпаргалка
Оператор select позволяет горутине ожидать выполнения нескольких операций с каналами одновременно. Он работает аналогично оператору switch, но каждый вариант (case) связан с отправкой или получением данных из канала:
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}Когда готовы несколько каналов, select выбирает один из них случайным образом. Без select получение данных из одного канала привело бы к блокировке и потенциальной потере данных из другого канала.
Блок default делает select неблокирующим — он выполняется немедленно, если ни один из каналов не готов:
select {
case msg := <-messages:
fmt.Println("Received:", msg)
default:
fmt.Println("No message available")
}Распространенный паттерн — сочетание select с таймаутами с помощью time.After, которая возвращает канал, получающий значение по истечении указанного времени:
select {
case result := <-ch:
fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
fmt.Println("Timeout!")
}Это предотвращает бесконечное ожидание программы в случаях, когда операция с каналом может никогда не завершиться.
Попробуйте сами
package main
import (
"fmt"
)
func main() {
var mode string
fmt.Scanln(&mode)
var serviceName string
fmt.Scanln(&serviceName)
var serviceStatus string
fmt.Scanln(&serviceStatus)
// TODO: Обработать различные режимы: "primary", "backup", "stop", "check"
// Для режимов "primary", "backup", "stop":
// - Создать три канала: primary, backup, done
// - Запустить goroutine, которая отправляет данные в соответствующий канал
// - Вызвать Monitor и вывести результат
// Для режима "check":
// - Прочитать дополнительный флаг ("send" или "nosend")
// - Создать буферизованный канал с емкостью 1
// - Если флаг равен "send", отправить обновление в канал
// - Вызвать CheckStatus и вывести результат
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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