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árioTipo recomendado
Sincronização entre goroutinesUnbuffered
Sinalização (done, quit)Unbuffered
Worker pool com fila de tarefasBuffered
Rate limitingBuffered
Produtor mais rápido que consumidorBuffered

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.

Veja também