Goroutines e channels: concurrency model
- ⬜🎯 Go mental model: simplicity first(Go Profissional)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Goroutine: thread barata do Go
Goroutine nasce com ~2 KB de stack e cresce sob demanda. O runtime Go escala M goroutines em N threads do SO (modelo M:N). Um processo Go segura milhões de goroutines em RAM moderada — esse é o superpoder do modelo.
Channels em três sabores
// Unbuffered: rendezvous
ch := make(chan int)
go func() { ch <- 42 }() // bloqueia até alguém receber
v := <-ch // desbloqueia o sender
// Buffered: fila com capacidade
jobs := make(chan Job, 100)
jobs <- j // não bloqueia até encher
// Channel direcional em assinatura (melhora API)
func producer(out chan<- int) { out <- 1 }
func consumer(in <-chan int) { v := <-in; _ = v }select: o heart do Go concorrente
func fetchWithTimeout(ctx context.Context, url string) (string, error) {
result := make(chan string, 1)
errCh := make(chan error, 1)
go func() {
body, err := doFetch(url)
if err != nil {
errCh <- err
return
}
result <- body
}()
select {
case body := <-result:
return body, nil
case err := <-errCh:
return "", err
case <-ctx.Done():
return "", ctx.Err()
}
}Esse é o padrão canônico: goroutine worker escreve em channel buffered 1 (evita leak se ninguém ler), select aguarda primeiro evento, ctx.Done oferece cancelamento cooperativo.
Fan-out / fan-in
func fanOutFanIn(urls []string) []Result {
jobs := make(chan string, len(urls))
results := make(chan Result, len(urls))
const workers = 8
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for u := range jobs {
results <- fetch(u)
}
}()
}
for _, u := range urls { jobs <- u }
close(jobs)
go func() { wg.Wait(); close(results) }()
out := make([]Result, 0, len(urls))
for r := range results { out = append(out, r) }
return out
}- → fecha quando zera
- Compute
- Integração de apps
- Conceito de arquitetura
- Segurança e identidade
O lema da linguagem — comunicar em vez de compartilhar — está desenhado aqui: nenhuma trava, nenhuma memória compartilhada, só canais. O que sobra de complexidade é encerramento e cancelamento, e são exatamente esses dois pontos que os defeitos exploram.
- 1 · Um canal, vários leitores — a distribuição é automática. Não é preciso repartir a fila: cada trabalhador pega o próximo disponível. O balanceamento sai de graça, e quem termina antes pega mais.
- 2 · Quem envia fecha; quem recebe percebe. Fechar do lado errado gera pânico. A regra evita o erro mais comum de quem começa: dois lados tentando encerrar o mesmo canal.
- 3 · O canal de resultados precisa de alguém para fechá-lo. Um contador espera todos terminarem e só então fecha. Sem isso, o consumidor espera para sempre por um resultado que não vem.
- 4 · Sem observar cancelamento, o trabalhador vaza. Ele fica esperando num canal que ninguém mais alimenta. A rotina nunca é recolhida, e a contagem sobe até estourar a memória.
- 5 · Canal com buffer não é otimização automática. Ele desacopla produtor e consumidor e esconde o desequilíbrio entre eles. Sem buffer, o produtor sente a pressão — que costuma ser a informação que você quer.
- 6 · O número de trabalhadores é decisão sua. Para trabalho de espera, muitos; para cálculo, perto do número de núcleos. Lançar uma rotina por item, sem limite, troca o gargalo de lugar em vez de resolvê-lo.
Qual é o modelo de concorrência que a linguagem incentiva?
Armadilhas comuns
1) goroutine que nunca retorna = leak (cada URL acima poderia vazar se alguém não fechasse jobs). 2) Escrever em channel fechado = panic. 3) Ler de channel nil = bloqueia para sempre. 4) range sobre channel só termina com close. Disciplina de lifecycle é meio caminho.
Race detector em CI
go test -race -count=1 ./...
# 2-10x mais lento, mas captura corridas escondidas
# rode sempre em PR, é o teste mais barato contra bug intermitenteRegra prática: se o teste passa sem -race e falha com -race, você tem bug real. Corrija com mutex, channel ou atomic — nunca ignore.
Perguntas frequentes
❓ O que é uma rotina leve e por que ela é barata?
❓ Canal ou mutex: qual usar em Go?
❓ O que causa vazamento de rotina?
Fixando
Qual é o erro mais comum ao usar rotinas leves?
Para que serve o contexto propagado entre chamadas?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…