O que é Channel em Go?
Um channel (canal) é o mecanismo principal de comunicação entre goroutines em Go. Channels permitem que goroutines enviem e recebam valores de forma segura e sincronizada, sem necessidade de locks explícitos. A filosofia do Go resume essa ideia na frase célebre:
“Não comunique compartilhando memória; compartilhe memória comunicando.”
Pense em um channel como um tubo tipado: de um lado, uma goroutine coloca dados; do outro lado, outra goroutine retira. O channel garante que essa troca aconteça de forma segura, sem condições de corrida. Isso torna a programação concorrente em Go significativamente mais simples e menos propensa a bugs do que abordagens tradicionais baseadas em mutexes.
Channels são tipados — um chan int só transporta inteiros, um chan string só transporta strings. Isso garante segurança de tipos em tempo de compilação.
Criando e usando channels
package main
import "fmt"
func main() {
// Cria um channel de strings
mensagens := make(chan string)
// Envia em uma goroutine
go func() {
mensagens <- "Olá do channel!"
}()
// Recebe no main
msg := <-mensagens
fmt.Println(msg) // Olá do channel!
}
O operador <- é usado tanto para enviar (ch <- valor) quanto para receber (valor := <-ch).
Unbuffered vs Buffered
Existem dois tipos fundamentais de channels em Go, e entender a diferença é essencial para escrever código concorrente correto.
Channels sem buffer (unbuffered)
Um channel sem buffer é criado com make(chan T) sem especificar capacidade. A operação de envio bloqueia até que outra goroutine esteja pronta para receber, e vice-versa. É uma comunicação síncrona — funciona como um ponto de encontro (rendezvous) entre goroutines.
ch := make(chan int) // channel sem buffer
go func() {
ch <- 42 // bloqueia até alguém receber
}()
valor := <-ch // bloqueia até alguém enviar
fmt.Println(valor) // 42
Channels com buffer (buffered)
Um channel com buffer é criado especificando a capacidade: make(chan T, capacidade). Envios não bloqueiam enquanto houver espaço no buffer. Recebimentos não bloqueiam enquanto houver dados no buffer.
ch := make(chan int, 3) // buffer para 3 valores
ch <- 1 // não bloqueia
ch <- 2 // não bloqueia
ch <- 3 // não bloqueia
// ch <- 4 // bloquearia, buffer cheio!
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
Quando usar cada um
| Cenário | Tipo recomendado |
|---|---|
| Sincronização entre goroutines | Unbuffered |
| Sinalização (done, quit) | Unbuffered |
| Worker pool com fila de tarefas | Buffered |
| Rate limiting | Buffered |
| Produtor mais rápido que consumidor | Buffered |
Direção de channels
Go permite restringir a direção de um channel em parâmetros de função. Isso aumenta a segurança e documenta a intenção do código:
// Só pode enviar
func produtor(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
// Só pode receber
func consumidor(ch <-chan int) {
for valor := range ch {
fmt.Println("Recebeu:", valor)
}
}
func main() {
ch := make(chan int, 5)
go produtor(ch)
consumidor(ch)
}
Tentar enviar em um channel de recebimento (ou vice-versa) causa um erro de compilação.
Fechando channels
Um channel pode ser fechado com close(ch) para sinalizar que não haverá mais envios. Recebimentos em um channel fechado retornam o valor zero do tipo imediatamente.
ch := make(chan int, 3)
ch <- 10
ch <- 20
close(ch)
// Verifica se o channel foi fechado
valor, aberto := <-ch
fmt.Println(valor, aberto) // 10 true
valor, aberto = <-ch
fmt.Println(valor, aberto) // 20 true
valor, aberto = <-ch
fmt.Println(valor, aberto) // 0 false (fechado, sem mais dados)
A forma idiomática de consumir todos os valores de um channel é com range:
for valor := range ch {
fmt.Println(valor)
}
// O loop encerra automaticamente quando o channel é fechado
Regra importante: apenas o remetente deve fechar o channel. Enviar para um channel fechado causa panic.
Select com channels
A instrução select permite esperar em múltiplas operações de channel simultaneamente. É como um switch, mas para channels:
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "resultado do ch1"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "resultado do ch2"
}()
select {
case msg1 := <-ch1:
fmt.Println("Recebido de ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("Recebido de ch2:", msg2)
case <-time.After(1 * time.Second):
fmt.Println("Timeout!")
}
}
O select é essencial para implementar padrões como timeout, cancelamento e multiplexação de channels.
Padrões comuns
Pipeline
Channels conectam estágios de processamento em sequência:
func gerar(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func dobrar(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * 2
}
close(out)
}()
return out
}
func main() {
nums := gerar(1, 2, 3, 4, 5)
dobrados := dobrar(nums)
for resultado := range dobrados {
fmt.Println(resultado) // 2, 4, 6, 8, 10
}
}
Para mais padrões como fan-out/fan-in e rate limiting, veja o artigo sobre padrões de concorrência em Go.
Perguntas Frequentes
Qual a diferença entre channel buffered e unbuffered?
Um channel unbuffered bloqueia tanto o envio quanto o recebimento até que ambas as goroutines estejam prontas — é uma comunicação síncrona. Um channel buffered permite que envios não bloqueiem enquanto há espaço no buffer, funcionando como uma fila assíncrona. Use unbuffered para sincronização e sinalização; use buffered quando o produtor é mais rápido que o consumidor ou quando precisa desacoplar goroutines.
O que acontece se enviar para um channel fechado?
Enviar para um channel fechado causa panic em tempo de execução. Por isso, a regra em Go é que apenas o remetente (produtor) deve fechar o channel, nunca o receptor. Se múltiplas goroutines enviam para o mesmo channel, coordene o fechamento usando sync.WaitGroup para garantir que todos os envios terminem antes de fechar.
Quando devo usar channel vs mutex?
Use channels quando a comunicação entre goroutines envolve transferência de dados ou coordenação de fluxo (pipeline, worker pool, sinalização). Use sync.Mutex quando precisa proteger acesso a dados compartilhados sem transferência (como incrementar um contador). A regra prática: se o dado precisa fluir entre goroutines, use channel; se o dado fica no mesmo lugar e múltiplas goroutines acessam, use mutex.
Como evito deadlock com channels?
Deadlock com channels geralmente ocorre quando todas as goroutines estão bloqueadas esperando umas pelas outras. As causas mais comuns são: enviar em um channel unbuffered sem receptor, receber de um channel sem emissor, e esquecer de fechar um channel usado com range. Use select com default ou time.After para evitar bloqueios indefinidos.