Channel
14. Channel
Analogia
Imagine uma esteira transportadora: uma pessoa coloca pacotes em uma extremidade e outra pessoa os retira na outra extremidade.
- A esteira transportadora é o channel
- Colocar um pacote é enviar (
ch <- valor) - Retirar um pacote é receber (
<-ch) - Quando a esteira está cheia, o remetente deve esperar; quando está vazia, o receptor deve esperar
Esta é a essência de um channel — um duto para comunicação segura entre goroutines.
Conceitos Fundamentais
| Conceito | Descrição |
|---|---|
| channel | Um duto de comunicação tipado, declarado com a palavra-chave chan |
| Envio | ch <- valor escreve dados no channel |
| Recebimento | valor := <-ch lê dados do channel |
| Channel sem buffer | Comunicação síncrona; remetente e receptor devem estar prontos ao mesmo tempo |
| Channel com buffer | Comunicação assíncrona; envio não bloqueia até o buffer estar cheio |
| close | Fecha o channel; nenhum envio adicional é permitido |
| range | Recebe continuamente de um channel até que ele seja fechado |
| Restrição de direção | Somente envio chan<- ou somente recebimento <-chan |
Filosofia do Go: Não comunique compartilhando memória; compartilhe memória comunicando.
Sintaxe Básica e Uso
Criando um Channel
// Channel sem buffer (síncrono)
ch := make(chan int)
// Channel com buffer (assíncrono, tamanho do buffer de 5)
ch := make(chan string, 5)
Enviando e Recebendo
ch := make(chan int)
// Enviando (em outra goroutine)
go func() {
ch <- 42 // envia 42 para o channel
}()
// Recebendo
valor := <-ch // recebe valor do channel, bloqueia até dados estarem disponíveis
fmt.Println(valor) // 42
Fechando um Channel
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch) // fecha o channel, nenhum envio adicional é permitido
Recebendo com range
ch := make(chan int, 3)
ch <- 10
ch <- 20
ch <- 30
close(ch)
// range continua recebendo até o channel ser fechado
for v := range ch {
fmt.Println(v) // 10 20 30
}
Restrições de Direção
// Channel somente de envio
func producer(ch chan<- int) {
ch <- 100
}
// Channel somente de recebimento
func consumer(ch <-chan int) {
v := <-ch
fmt.Println(v)
}
💡 Dicas
- Channels sem buffer garantem sincronização entre remetente e receptor — os dados são transferidos diretamente de um para o outro
- Channels com buffer bloqueiam no envio quando o buffer está cheio e bloqueiam no recebimento quando o buffer está vazio
- Enviar para um channel fechado causa um panic
- Receber de um channel fechado retorna o valor zero sem bloquear
- Não feche um channel do lado do receptor; o remetente deve fechá-lo (a menos que haja apenas um remetente)
- Use
v, ok := <-chpara verificar se um channel está fechado (okéfalsequando fechado e não há dados restantes)
Exemplos
Exemplo: Comunicação Básica entre Goroutines (Dificuldade ⭐)
package main
import "fmt"
func main() {
// Cria um channel sem buffer
ch := make(chan string)
// Lança uma goroutine para enviar uma mensagem
go func() {
ch <- "Olá, thread principal!"
}()
// Goroutine principal recebe a mensagem (bloqueia enquanto espera)
msg := <-ch
fmt.Println(msg) // Olá, thread principal!
}
Explicação: Um channel sem buffer sincroniza duas goroutines — o remetente bloqueia até que o receptor esteja pronto.
Exemplo: Padrão Produtor-Consumidor (Dificuldade ⭐⭐)
package main
import "fmt"
// Produtor: gera dados e envia para o channel (somente envio)
func producer(id int, ch chan<- int, count int) {
for i := 0; i < count; i++ {
value := id*100 + i
fmt.Printf("Produtor %d: enviando %d\n", id, value)
ch <- value
}
}
// Consumidor: recebe dados do channel e processa (somente recebimento)
func consumer(id int, ch <-chan int) {
for v := range ch {
fmt.Printf("Consumidor %d: processando %d\n", id, v)
}
fmt.Printf("Consumidor %d: channel fechado, saindo\n", id)
}
func main() {
// Cria um channel com buffer
ch := make(chan int, 5)
// Inicia 2 produtores
go producer(1, ch, 3)
go producer(2, ch, 3)
// Inicia 2 consumidores
go consumer(1, ch)
go consumer(2, ch)
// Espera todos os produtores terminarem (em projetos reais, use sync.WaitGroup)
// Aqui simplesmente usamos sleep para demonstração
import_time := time.After(2 * time.Second)
<-import_time
close(ch) // fecha o channel
// Dá tempo aos consumidores para processar dados restantes
time.Sleep(500 * time.Millisecond)
fmt.Println("Todo o trabalho concluído")
}
Explicação:
- Channels com buffer permitem que produtores continuem produzindo enquanto o buffer não estiver cheio, sem esperar pelos consumidores
range chcontinua recebendo até o channel ser fechado- Restrições de direção
chan<-e<-chanprevinem uso indevido em tempo de compilação
Exemplo: Semáforo com Channel e Fan-out/Fan-in (Dificuldade ⭐⭐⭐)
package main
import (
"fmt"
"sync"
"time"
)
// worker processa tarefas e envia resultados para o channel de resultados
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d: iniciou processamento da tarefa %d\n", id, job)
time.Sleep(time.Duration(100+id*50) * time.Millisecond) // simula trabalho
result := job * job
fmt.Printf("Worker %d: tarefa %d concluída, resultado %d\n", id, job, result)
results <- result
}
}
func main() {
numJobs := 10
numWorkers := 3
jobs := make(chan int, numJobs) // channel de tarefas
results := make(chan int, numJobs) // channel de resultados
// Inicia pool de workers
var wg sync.WaitGroup
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Envia tarefas
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs) // fecha o channel de tarefas; os loops range dos workers vão encerrar
// Fecha o channel de resultados após todos os workers terminarem
go func() {
wg.Wait()
close(results)
}()
// Coleta todos os resultados
total := 0
for r := range results {
total += r
}
fmt.Printf("Soma de todos os resultados: %d\n", total)
}
Explicação:
- Fan-out: múltiplos workers leem do mesmo channel de tarefas
- Fan-in: múltiplos workers escrevem no mesmo channel de resultados
WaitGroupgarante que todos os workers terminem antes de fechar o channel de resultados- Este é um padrão comum de pool de workers concorrentes em Go
Casos de Uso Práticos
Caso 1: Controle de Timeout
package main
import (
"fmt"
"time"
)
func slowOperation(ch chan<- string) {
time.Sleep(3 * time.Second) // simula uma operação lenta
ch <- "Operação concluída"
}
func main() {
ch := make(chan string, 1)
go slowOperation(ch)
// Usa select para controle de timeout
select {
case result := <-ch:
fmt.Println("Resultado recebido:", result)
case <-time.After(2 * time.Second):
fmt.Println("Operação excedeu o tempo!") // dispara após 2 segundos
}
}
Explicação: time.After retorna um channel que envia um valor após a duração especificada. Combinado com select, permite controle de timeout elegante.
Caso 2: Limitação de Taxa para Crawlers Concorrentes
package main
import (
"fmt"
"time"
)
// fetchURL simula buscar uma URL
func fetchURL(url string) string {
time.Sleep(500 * time.Millisecond) // simula latência de rede
return "Conteúdo da página: " + url
}
func main() {
urls := []string{
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3",
"https://example.com/page4",
"https://example.com/page5",
}
// Usa um channel com buffer como semáforo, limitando concorrência a 2
semaphore := make(chan struct{}, 2)
results := make(chan string, len(urls))
for _, url := range urls {
go func(u string) {
semaphore <- struct{}{} // adquire semáforo (bloqueia quando cheio)
fmt.Printf("Iniciando busca: %s\n", u)
result := fetchURL(u)
results <- result
<-semaphore // libera semáforo
}(url)
}
// Coleta resultados
for i := 0; i < len(urls); i++ {
fmt.Println(<-results)
}
fmt.Println("Todas as páginas buscadas")
}
Explicação: Um channel com buffer funciona como semáforo, limitando o número de goroutines executando concorrentemente e prevenindo que muitas requisições concorrentes sobrecarreguem o servidor alvo.
❓ Perguntas Frequentes
P1: O que acontece se você enviar dados para um channel fechado?
Causa panic. Qualquer operação de envio em um channel fechado dispara panic: send on closed channel. Receber não causa panic, mas retorna o valor zero.
ch := make(chan int, 1)
ch <- 1
close(ch)
// ch <- 2 // ❌ panic: send on closed channel
v := <-ch // ✅ retorna 1 (valor restante no buffer)
v2 := <-ch // ✅ retorna 0 (valor zero, channel vazio e fechado)
Boa prática: O remetente deve fechar o channel, não o receptor. Se múltiplos remetentes compartilham um channel, use sync.Once ou um channel de done adicional para coordenar o fechamento.
P2: E um channel nil?
Envio e recebimento ambos bloqueiam permanentemente; fechar um channel nil causa panic.
var ch chan int // channel nil
// ch <- 1 // ❌ bloqueia permanentemente
// v := <-ch // ❌ bloqueia permanentemente
// close(ch) // ❌ panic: close of nil channel
Um channel nil é útil em select — o case correspondente é automaticamente ignorado:
var ch chan int // channel nil
select {
case v := <-ch: // ignorado (channel nil)
fmt.Println(v)
case <-time.After(1 * time.Second):
fmt.Println("Tempo esgotado")
}
P3: Qual a diferença entre channels sem buffer e com buffer?
| Recurso | Sem buffer make(chan T) |
Com buffer make(chan T, n) |
|---|---|---|
| Envio bloqueia quando | Receptor não está pronto | Buffer está cheio |
| Recebimento bloqueia quando | Remetente não está pronto | Buffer está vazio |
| Sincronicidade | Síncrono (rendezvous) | Assíncrono (até buffer encher) |
| Uso típico | Notificação de sinal, sync | Produtor-consumidor, fila |
P4: Como verificar se um channel está fechado?
Use o segundo valor de retorno da operação de recebimento:
ch := make(chan int, 1)
ch <- 42
close(ch)
v, ok := <-ch
fmt.Println(v, ok) // 42 true
v2, ok := <-ch
fmt.Println(v2, ok) // 0 false (fechado e sem dados)
ok é false quando o channel está fechado e não há dados restantes no buffer.
📖 Resumo
| Ponto-chave | Detalhe |
|---|---|
| Criando um channel | make(chan T) sem buffer, make(chan T, n) com buffer |
| Enviando | ch <- valor, condição de bloqueio depende do tipo de buffer |
| Recebendo | valor := <-ch ou v, ok := <-ch |
| Fechando | close(ch), responsabilidade do remetente, não pode enviar após fechar |
| range | for v := range ch continua recebendo até ser fechado |
| Restrição de direção | chan<- somente envio, <-chan somente recebimento |
| select | Multiplexação, opera múltiplas operações de channel |
| Boa prática | Prefira sync.WaitGroup para esperar goroutines completarem; evite fechamento duplo |
Channels são o núcleo da concorrência em Go. Entendendo sem buffer vs com buffer, close/range, restrições de direção e select (próxima lição), você terá dominado a essência da comunicação concorrente do Go.
📝 Exercícios
Exercício 1: Fundamentos de Channels
Escreva um programa que inicia 3 goroutines, cada uma computando o quadrado de um número e enviando o resultado para um channel. A goroutine principal recebe e imprime todos os resultados.
Requisitos:
- Use um channel sem buffer
- Use
WaitGrouppara garantir que todas as goroutines completem - A saída deve ser algo como:
Resultados: [1, 4, 9]
Exercício 2: Implementar Fan-in com Channels
Escreva uma função merge(channels ...<-chan int) <-chan int que mescla múltiplos channels somente de recebimento em um único channel somente de recebimento.
Requisitos:
- Entrada: múltiplos
<-chan int - Saída: um
<-chan intcontendo dados de todos os channels de entrada - Implemente usando goroutines e
WaitGroup - Quando todos os channels de entrada forem fechados, o channel de saída também deve ser fechado
Exercício 3: Pool de Workers com Timeout
Implemente um pool de workers com as seguintes características:
- Inicie N goroutines workers
- Cada tarefa tem um timeout de 500ms
- Se uma tarefa exceder o tempo, registre e pule
- Conte tarefas bem-sucedidas e que excederam o tempo
Dica: Combine channels, select e time.After.



