Instrução Select
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 62 de 107.
A instrução select permite que uma goroutine espere por múltiplas operações de canal simultaneamente. É como uma instrução switch, mas cada case envolve um envio ou recebimento de canal. Quando múltiplos canais estão prontos, o select escolhe um aleatoriamente.
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)
}
}Sem o select, receber de ch1 bloquearia até que os dados chegassem, potencialmente perdendo dados em ch2. Com o select, o canal que receber dados primeiro será processado.
O caso default torna o select não bloqueante — ele executa imediatamente se nenhum canal estiver pronto:
select {
case msg := <-messages:
fmt.Println("Received:", msg)
default:
fmt.Println("No message available")
}Um padrão comum combina select com timeouts usando time.After, que retorna um canal que recebe um valor após a duração especificada:
select {
case result := <-ch:
fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
fmt.Println("Timeout!")
}Isso evita que seu programa espere indefinidamente quando uma operação de canal pode nunca ser concluída.
Desafio
FácilVamos construir um monitor de serviço que utiliza a instrução select para lidar com múltiplas fontes de dados concorrentes. Seu monitor ouvirá diferentes canais de serviço simultaneamente e responderá apropriadamente quando os dados chegarem ou quando não houver atualizações disponíveis.
Você organizará seu código em dois arquivos:
monitor.go: Defina sua lógica de monitoramento de serviço com manipulação de canais baseada em select.Crie uma struct
ServiceUpdatecom os camposName(string) eStatus(string).Implemente uma função
Monitorque recebe três canais:primary chan ServiceUpdate,backup chan ServiceUpdateedone chan bool. Esta função deve usarselectpara ouvir os três canais simultaneamente:- Ao receber de
primary: retorne a string formatada"Primary: [Name] is [Status]" - Ao receber de
backup: retorne a string formatada"Backup: [Name] is [Status]" - Ao receber de
done: retorne"Monitoring stopped"
Implemente uma função
CheckStatusque recebe um únicoupdates chan ServiceUpdatee usaselectcom um casodefaultpara realizar uma verificação não bloqueante:- Se uma atualização estiver disponível: retorne
"Update available: [Name]" - Se nenhuma atualização estiver disponível (caso default): retorne
"No updates pending"
- Ao receber de
main.go: Configure os canais e demonstre o comportamento do select bloqueante e não bloqueante.Leia um modo da entrada que determina qual cenário executar:
"primary","backup","stop"ou"check".Para os modos
"primary","backup"e"stop": leia também um nome de serviço e um status. Crie os três canais, inicie uma goroutine que envia dados para o canal apropriado com base no modo, então chameMonitore imprima seu resultado.Para o modo
"check": leia um nome de serviço e um status, além de uma flag ("send"ou"nosend") indicando se deve enviar dados antes de verificar. Crie um canal com buffer de capacidade 1. Se a flag for"send", envie uma atualização para o canal antes de chamarCheckStatus. Imprima o resultado deCheckStatus.
As seguintes entradas serão fornecidas:
- Linha 1: Modo (
"primary","backup","stop"ou"check") - Linha 2: Nome do serviço
- Linha 3: Status do serviço
- Linha 4 (apenas para o modo
"check"):"send"ou"nosend"
Por exemplo, dado:
primary
Database
healthySua saída deve ser:
Primary: Database is healthyE dado:
check
Cache
active
nosendSua saída deve ser:
No updates pendingFolha de consulta
A instrução select permite que uma goroutine aguarde múltiplas operações de canal simultaneamente. Ela funciona como uma instrução switch, mas cada caso envolve um envio ou recebimento de canal:
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}Quando múltiplos canais estão prontos, o select escolhe um aleatoriamente. Sem o select, receber de um canal bloquearia e potencialmente perderia dados em outro canal.
O caso default torna o select não bloqueante — ele executa imediatamente se nenhum canal estiver pronto:
select {
case msg := <-messages:
fmt.Println("Received:", msg)
default:
fmt.Println("No message available")
}Um padrão comum combina o select com timeouts usando time.After, que retorna um canal que recebe um valor após a duração especificada:
select {
case result := <-ch:
fmt.Println("Got result:", result)
case <-time.After(2 * time.Second):
fmt.Println("Timeout!")
}Isso evita que seu programa espere para sempre quando uma operação de canal pode nunca ser concluída.
Experimente você mesmo
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: Lidar com diferentes modos: "primary", "backup", "stop", "check"
// Para os modos "primary", "backup", "stop":
// - Criar três canais: primary, backup, done
// - Iniciar uma goroutine que envia dados para o canal apropriado
// - Chamar Monitor e imprimir o resultado
// Para o modo "check":
// - Ler a flag adicional ("send" ou "nosend")
// - Criar um canal com buffer de capacidade 1
// - Se a flag for "send", enviar uma atualização para o canal
// - Chamar CheckStatus e imprimir o resultado
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool