Sentencia Select
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 62 de 107.
La sentencia select permite que una goroutine espere en múltiples operaciones de canal simultáneamente. Es como una sentencia switch, pero cada caso involucra un envío o recepción de canal. Cuando múltiples canales están listos, select elige uno al azar.
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)
}
}Sin select, recibir de ch1 bloquearía hasta que lleguen los datos, perdiendo potencialmente datos en ch2. Con select, el canal que reciba datos primero será procesado.
El caso default hace que select sea no bloqueante; se ejecuta inmediatamente si ningún canal está listo:
select {
case msg := <-messages:
fmt.Println("Received:", msg)
default:
fmt.Println("No message available")
}Un patrón común combina select con tiempos de espera usando time.After, el cual devuelve un canal que recibe un valor después de la duración especificada:
select {
case result := <-ch:
fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
fmt.Println("Timeout!")
}Esto evita que tu programa espere indefinidamente cuando una operación de canal podría no completarse nunca.
Desafío
FácilVamos a construir un monitor de servicios que utiliza la sentencia select para manejar múltiples fuentes de datos concurrentes. Tu monitor escuchará diferentes canales de servicio simultáneamente y responderá adecuadamente cuando lleguen datos o cuando no haya actualizaciones disponibles.
Organizarás tu código en dos archivos:
monitor.go: Define tu lógica de monitoreo de servicios con manejo de canales basado en select.Crea un struct
ServiceUpdatecon los camposName(string) yStatus(string).Implementa una función
Monitorque reciba tres canales:primary chan ServiceUpdate,backup chan ServiceUpdate, ydone chan bool. Esta función debe usarselectpara escuchar en los tres canales simultáneamente:- Al recibir de
primary: devuelve el string formateado"Primary: [Name] is [Status]" - Al recibir de
backup: devuelve el string formateado"Backup: [Name] is [Status]" - Al recibir de
done: devuelve"Monitoring stopped"
Implementa una función
CheckStatusque reciba un únicoupdates chan ServiceUpdatey useselectcon un casodefaultpara realizar una comprobación no bloqueante:- Si hay una actualización disponible: devuelve
"Update available: [Name]" - Si no hay ninguna actualización disponible (caso default): devuelve
"No updates pending"
- Al recibir de
main.go: Configura los canales y demuestra el comportamiento de select tanto bloqueante como no bloqueante.Lee un modo de la entrada que determine qué escenario ejecutar:
"primary","backup","stop", o"check".Para los modos
"primary","backup", y"stop": lee también un nombre de servicio y un estado. Crea los tres canales, lanza una goroutine que envíe datos al canal apropiado según el modo, luego llama aMonitore imprime su resultado.Para el modo
"check": lee un nombre de servicio y un estado, además de una bandera ("send"o"nosend") que indique si se deben enviar datos antes de la comprobación. Crea un canal con búfer de capacidad 1. Si la bandera es"send", envía una actualización al canal antes de llamar aCheckStatus. Imprime el resultado deCheckStatus.
Se proporcionarán las siguientes entradas:
- Línea 1: Modo (
"primary","backup","stop", o"check") - Línea 2: Nombre del servicio
- Línea 3: Estado del servicio
- Línea 4 (solo para el modo
"check"):"send"o"nosend"
Por ejemplo, dado:
primary
Database
healthyTu salida debería ser:
Primary: Database is healthyY dado:
check
Cache
active
nosendTu salida debería ser:
No updates pendingHoja de referencia
La sentencia select permite que una goroutine espere en múltiples operaciones de canal simultáneamente. Funciona de manera similar a una sentencia switch, pero cada caso involucra el envío o la recepción de un canal:
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}Cuando múltiples canales están listos, select elige uno al azar. Sin select, recibir de un canal bloquearía la ejecución y potencialmente se perderían datos de otro canal.
El caso default hace que select sea no bloqueante: se ejecuta inmediatamente si ningún canal está listo:
select {
case msg := <-messages:
fmt.Println("Received:", msg)
default:
fmt.Println("No message available")
}Un patrón común combina select con tiempos de espera (timeouts) usando time.After, que devuelve un canal que recibe un valor después de la duración especificada:
select {
case result := <-ch:
fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
fmt.Println("Timeout!")
}Esto evita que su programa espere indefinidamente cuando una operación de canal podría no completarse nunca.
Pruébalo tú mismo
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: Manejar diferentes modos: "primary", "backup", "stop", "check"
// Para los modos "primary", "backup", "stop":
// - Crear tres canales: primary, backup, done
// - Lanzar una goroutine que envíe datos al canal apropiado
// - Llamar a Monitor e imprimir el resultado
// Para el modo "check":
// - Leer el flag adicional ("send" o "nosend")
// - Crear un canal con búfer con capacidad 1
// - Si el flag es "send", enviar una actualización al canal
// - Llamar a CheckStatus e imprimir el resultado
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool