Channel

14. Channel

Analogia

Imagine uma esteira transportadora: uma pessoa coloca pacotes em uma extremidade e outra pessoa os retira na outra extremidade.

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

GO
// 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

GO
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

GO
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch) // fecha o channel, nenhum envio adicional é permitido

Recebendo com range

GO
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

GO
// 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


Exemplos

Exemplo: Comunicação Básica entre Goroutines (Dificuldade ⭐)

GO
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!
}
▶ Experimente

Explicação: Um channel sem buffer sincroniza duas goroutines — o remetente bloqueia até que o receptor esteja pronto.


Exemplo: Padrão Produtor-Consumidor (Dificuldade ⭐⭐)

GO
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")
}
▶ Experimente

Explicação:


Exemplo: Semáforo com Channel e Fan-out/Fan-in (Dificuldade ⭐⭐⭐)

GO
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)
}
▶ Experimente

Explicação:


Casos de Uso Práticos

Caso 1: Controle de Timeout

GO
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

GO
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.

GO
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.

GO
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:

GO
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:

GO
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:


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:


Exercício 3: Pool de Workers com Timeout

Implemente um pool de workers com as seguintes características:

Dica: Combine channels, select e time.After.


Próxima Lição

15. Select →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%