Canais e Comunicação
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 60 de 107.
Canais são o mecanismo principal do Go para comunicação entre goroutines. Em vez de compartilhar memória diretamente, as goroutines enviam e recebem valores através de canais, seguindo a filosofia do Go: "Não se comunique compartilhando memória; compartilhe memória se comunicando."
Crie um canal usando a função make com a palavra-chave chan:
messages := make(chan string)Use o operador de seta <- para enviar e receber valores. A seta aponta na direção do fluxo de dados:
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // enviar para o canal
}()
msg := <-messages // receber do canal
fmt.Println(msg) // Saída: hello
}Por padrão, envios e recebimentos bloqueiam até que o outro lado esteja pronto. Quando a goroutine envia "hello", ela espera até que main esteja pronta para receber. Este comportamento de bloqueio fornece sincronização integrada—sem a necessidade de time.Sleep.
Você pode fechar um canal para sinalizar que não serão enviados mais valores. Os receptores podem detectar isso:
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}Canais são tipados—um chan string só pode carregar strings. Essa segurança de tipo garante que as goroutines se comuniquem com os tipos de dados corretos, capturando erros em tempo de compilação em vez de tempo de execução.
Desafio
FácilVamos construir um sistema de retransmissão de mensagens que demonstra como as goroutines se comunicam através de canais. Você criará um pipeline onde as mensagens fluem entre diferentes estágios de processamento, com cada estágio sendo executado de forma concorrente.
Você organizará seu código em dois arquivos:
relay.go: Defina seu pipeline de processamento de mensagens.Crie uma struct
Messagecom os camposID(int) eContent(string).Implemente três funções que representam diferentes estágios do seu pipeline:
Producer(messages []Message, out chan Message)- Recebe um slice de mensagens e envia cada uma para o canal de saída. Após enviar todas as mensagens, feche o canal para sinalizar a conclusão.Transformer(in chan Message, out chan Message)- Recebe mensagens do canal de entrada, transforma o conteúdo de cada mensagem para letras maiúsculas e envia a mensagem transformada para o canal de saída. Quando o canal de entrada for fechado (detectado usando a forma de recebimento de dois valores), feche o canal de saída.Consumer(in chan Message) []string- Recebe todas as mensagens do canal de entrada e as coleta em um slice de strings formatadas. Cada string deve seguir o formato:Message [ID]: [Content]. Retorne o slice quando o canal for fechado.
main.go: Configure o pipeline e orquestre o fluxo concorrente de mensagens.Leia o número de mensagens, depois leia o ID e o conteúdo de cada mensagem. Crie as mensagens e configure dois canais para conectar seus três estágios do pipeline. Inicie o Producer e o Transformer como goroutines e, em seguida, execute o Consumer na goroutine principal para coletar os resultados. Imprima cada resultado em uma linha separada.
As seguintes entradas serão fornecidas:
- Linha 1: Número de mensagens (inteiro)
- Linhas seguintes: Para cada mensagem, duas linhas - o ID da mensagem (inteiro) e, em seguida, seu conteúdo (string)
Por exemplo, dado:
2
1
hello world
2
go channelsSua saída deve ser:
Message 1: HELLO WORLD
Message 2: GO CHANNELSO pipeline flui assim: Producer envia mensagens → Transformer converte para maiúsculas → Consumer coleta e formata os resultados. Cada seta representa um canal conectando estágios concorrentes. Use o pacote strings para a transformação em maiúsculas.
Folha de consulta
Canais são o principal mecanismo do Go para comunicação entre goroutines. Crie um canal usando make com a palavra-chave chan:
messages := make(chan string)Use o operador de seta <- para enviar e receber valores. A seta aponta na direção do fluxo de dados:
messages <- "hello" // enviar para o canal
msg := <-messages // receber do canalPor padrão, envios e recebimentos bloqueiam até que o outro lado esteja pronto, fornecendo sincronização integrada:
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // enviar para o canal
}()
msg := <-messages // receber do canal
fmt.Println(msg) // Saída: hello
}Feche um canal para sinalizar que não serão enviados mais valores. Os receptores podem detectar o fechamento usando a forma de recebimento de dois valores:
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}Canais são tipados—um chan string só pode carregar strings, fornecendo segurança de tipo em tempo de compilação.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Ler o número de mensagens
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// Ler as mensagens
messages := make([]Message, n)
for i := 0; i < n; i++ {
idLine, _ := reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(idLine))
contentLine, _ := reader.ReadString('\n')
content := strings.TrimSpace(contentLine)
messages[i] = Message{ID: id, Content: content}
}
// TODO: Criar dois canais para conectar os estágios do pipeline
// TODO: Iniciar o Producer como uma goroutine
// TODO: Iniciar o Transformer como uma goroutine
// TODO: Executar o Consumer na goroutine principal e coletar os resultados
// TODO: Imprimir cada resultado em uma linha separada
}
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