Concorrência em Go

Uma das maiores forças do Go é seu modelo de concorrência, baseado em goroutines e channels. Diferente de threads tradicionais, goroutines são leves e gerenciadas pelo runtime do Go.

Goroutines

Uma goroutine é uma função executando concorrentemente com outras goroutines. Para criar uma goroutine, use a palavra-chave go:

package main

import (
    "fmt"
    "time"
)

func dizer(msg string) {
    for i := 0; i < 3; i++ {
        fmt.Println(msg)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go dizer("goroutine")  // executa concorrentemente
    dizer("main")          // executa na goroutine principal
}

Características das Goroutines

  • Leves: ~2KB de stack (vs ~1MB para threads OS)
  • Multiplexadas: milhares podem rodar em poucos threads OS
  • Gerenciadas pelo runtime: scheduling automático
// Criar 1000 goroutines é trivial
for i := 0; i < 1000; i++ {
    go func(n int) {
        fmt.Println(n)
    }(i)
}

Channels

Channels são a forma idiomática de comunicação entre goroutines. Seguem o princípio:

“Não comunique compartilhando memória; compartilhe memória comunicando.”

Criando e Usando Channels

// Criar channel
ch := make(chan int)

// Enviar valor
go func() {
    ch <- 42  // envia 42 para o channel
}()

// Receber valor
valor := <-ch  // recebe do channel
fmt.Println(valor)  // 42

Channels Bufferizados

// Channel com buffer de tamanho 3
ch := make(chan int, 3)

ch <- 1  // não bloqueia
ch <- 2  // não bloqueia
ch <- 3  // não bloqueia
ch <- 4  // BLOQUEIA (buffer cheio)

Direção de Channels

// Apenas envio
func produtor(ch chan<- int) {
    ch <- 1
}

// Apenas recebimento
func consumidor(ch <-chan int) {
    valor := <-ch
}

Fechando Channels

ch := make(chan int)

go func() {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)  // sinaliza que não há mais valores
}()

// Range itera até o channel fechar
for v := range ch {
    fmt.Println(v)
}

Verificando se Channel está Fechado

valor, ok := <-ch
if !ok {
    fmt.Println("channel fechado")
}

Select

O select permite esperar em múltiplos channels:

select {
case msg1 := <-ch1:
    fmt.Println("recebeu de ch1:", msg1)
case msg2 := <-ch2:
    fmt.Println("recebeu de ch2:", msg2)
case ch3 <- valor:
    fmt.Println("enviou para ch3")
default:
    fmt.Println("nenhum channel pronto")
}

Timeout com Select

select {
case res := <-ch:
    fmt.Println(res)
case <-time.After(1 * time.Second):
    fmt.Println("timeout!")
}

Padrões Comuns

Worker Pool

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("worker %d processando job %d\n", id, j)
        time.Sleep(time.Second)
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // Criar 3 workers
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // Enviar 5 jobs
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    // Coletar resultados
    for a := 1; a <= 5; a++ {
        <-results
    }
}

Fan-out / Fan-in

// Fan-out: uma fonte, múltiplos consumidores
func fanOut(input <-chan int, workers int) []<-chan int {
    outputs := make([]<-chan int, workers)
    for i := 0; i < workers; i++ {
        outputs[i] = processador(input)
    }
    return outputs
}

// Fan-in: múltiplas fontes, um consumidor
func fanIn(inputs ...<-chan int) <-chan int {
    out := make(chan int)
    var wg sync.WaitGroup
    
    for _, in := range inputs {
        wg.Add(1)
        go func(ch <-chan int) {
            defer wg.Done()
            for v := range ch {
                out <- v
            }
        }(in)
    }
    
    go func() {
        wg.Wait()
        close(out)
    }()
    
    return out
}

Pipeline

func gerador(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func quadrado(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func main() {
    // Pipeline: gerador -> quadrado
    for v := range quadrado(gerador(1, 2, 3, 4)) {
        fmt.Println(v)  // 1, 4, 9, 16
    }
}

sync.WaitGroup

Para esperar múltiplas goroutines terminarem:

var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Println(n)
    }(i)
}

wg.Wait()  // espera todas terminarem

Armadilhas Comuns

1. Race Condition

// ERRADO - race condition
counter := 0
for i := 0; i < 1000; i++ {
    go func() {
        counter++  // múltiplas goroutines acessando
    }()
}

// CORRETO - usando mutex
var mu sync.Mutex
counter := 0
for i := 0; i < 1000; i++ {
    go func() {
        mu.Lock()
        counter++
        mu.Unlock()
    }()
}

2. Goroutine Leak

// ERRADO - goroutine nunca termina
func leak() {
    ch := make(chan int)
    go func() {
        val := <-ch  // bloqueia para sempre
        fmt.Println(val)
    }()
    // ch nunca recebe valor, goroutine fica pendurada
}

// CORRETO - usar contexto para cancelamento
func semLeak(ctx context.Context) {
    ch := make(chan int)
    go func() {
        select {
        case val := <-ch:
            fmt.Println(val)
        case <-ctx.Done():
            return  // sai quando contexto cancela
        }
    }()
}

3. Fechar Channel do Lado Errado

// REGRA: quem ENVIA fecha o channel
// Nunca feche um channel do lado receptor

Detectando Race Conditions

# Build com detector de race
go build -race ./...

# Testar com detector de race
go test -race ./...

Recursos


Última atualização: Janeiro 2026


Veja também