Concorrência em Produção: Padrões Além do Hello Goroutine
Um go func() em um tutorial prova que o runtime funciona.
Busque em todas as páginas da documentação
Um go func() em um tutorial prova que o runtime funciona.
Um serviço em produção deve limitar o paralelismo, propagar o cancelamento, desviar carga sob pressão e encerrar sem deixar goroutines bloqueadas para sempre.
Esta página é a âncora conceitual para Concorrência Avançada.
Fundamentos de Concorrência Avançada coleta trechos executáveis; artigos irmãos cobrem pools de workers, pipelines, limitação de taxa, errgroup, atômicos, prevenção de vazamentos e hábitos de revisão.
Pense em um serviço Go como um controlador de tráfego para o trabalho.
Requisições chegam mais rápido do que um banco de dados pode responder se cada requisição gerar sua própria árvore de goroutines ilimitada.
O código de produção introduz capacidade: um número fixo de workers, um semáforo limitando chamadas em voo, ou um canal bufferizado que bloqueia produtores quando consumidores ficam para trás.
Cancelamento é o segundo pilar.
context.Context atravessa um sinal de parada do handler HTTP por todas as chamadas downstream.
Quando o cliente desconecta ou um deadline expira, o trabalho em voo deve sair prontamente em vez de manter conexões abertas.
O terceiro pilar é composição.
Padrões simples se combinam em sistemas maiores:
Estas não são religiões concorrentes.
Um único handler pode usar um semáforo para HTTP de saída, errgroup para buscas paralelas e um pool de workers para retentativas em segundo plano.
Requisição do cliente
|
v
Handler HTTP (ctx de r.Context())
|
+----+----+
| |
v v
errgroup semáforo limitado
paralelo chamadas downstream
fetches
| |
+----+----+
v
merge / responder
|
em ctx.Done() -> parar de gerar, drenar ou abandonar
Pools de workers limitam o paralelismo CPU-bound ou I/O-bound a um número que você escolhe - frequentemente ligado ao tamanho do pool de conexões ou contagem de CPUs.
Jobs entram em um canal; workers iteram sobre ele até que o canal feche ou o contexto cancele.
Pipelines separam preocupações: parsing, validação, enriquecimento e persistência podem cada um rodar em seu próprio estágio com buffers limitados entre eles.
Backpressure aparece naturalmente quando um estágio upstream bloqueia no envio porque o buffer downstream está cheio.
errgroup coordena um lote de goroutines com cancelamento compartilhado: o primeiro erro cancela irmãos e retorna um erro combinado para o chamador.
Emparelhe-o com context.WithCancel derivado do contexto da requisição.
Limitação de taxa (token bucket, golang.org/x/time/rate, ou um semáforo ponderado) protege recursos compartilhados.
Shutdown gracioso escuta por SIGTERM, para de aceitar novo trabalho, espera por requisições em voo com um timeout, então fecha os canais de workers para que as goroutines saiam.
| Padrão | Força | Fraqueza | Melhor Encaixe |
|---|---|---|---|
go ilimitado por tarefa | Código mais simples | Sem backpressure; Risco de OOM | Apenas protótipos |
| Pool de workers | Paralelismo previsível | Ajuste do tamanho da fila | Pools CPU/IO, processadores de jobs |
| Estágios de pipeline | Fluxo de dados claro | Mais partes móveis | ETL, processamento de logs |
| errgroup | Cancelamento no primeiro erro | Menos controle sobre resultados parciais | Buscas paralelas |
| Semáforo / limitação de taxa | Protege downstream | Adiciona latência sob carga | Gateways de DB/API |
Servidores HTTP em net/http já rodam cada requisição em sua própria goroutine.
O trabalho avançado está dentro do handler: limite chamadas concorrentes para Postgres, Redis ou uma API parceira.
Combine o tamanho do pool com SetMaxOpenConns em database/sql para não enfileirar goroutines atrás de um pool esgotado.
Para RPCs de streaming ou WebSockets, uma goroutine de longa duração por conexão é normal.
Ainda assim, limite trabalho auxiliar (refresh em segundo plano, enriquecimento fan-out) com semáforos.
Observabilidade fecha o ciclo: rastreie a contagem de goroutines (runtime.NumGoroutine), requisições em voo, profundidade da fila e tempo bloqueado em semáforos.
Picos sem crescimento de tráfego frequentemente sinalizam um vazamento ou deadlock.
Execute testes de carga com -race em CI para pacotes que mutam estado compartilhado.
Kubernetes envia SIGTERM antes de SIGKILL.
Seu main deve chamar http.Server.Shutdown com um deadline, cancelar um contexto raiz, fechar canais de jobs e Wait() nos grupos de workers.
Documente a propriedade: quem fecha qual canal, quem espera por quem.
Integração com contexto é obrigatória para handlers de produção.
Passe r.Context() para http.NewRequestWithContext, chamadas gRPC e db.QueryContext.
Veja Pacote context para o modelo de cancelamento.
sync.Mutex ou atômicos; canais são para passar propriedade, não cada byte compartilhado.sync.WaitGroup ainda é correto quando erros são tratados por goroutine e você não precisa de cancelamento automático ao ocorrer erro.ctx.Done(); o fechamento sozinho não desbloqueia envios esperando em um buffer cheio.Um semáforo limitado ou um pequeno pool de workers para I/O de saída.
É a menor mudança que impede goroutines ilimitadas quando o tráfego aumenta.
GOMAXPROCS limita as threads do SO executando código Go.
Pools de workers limitam tarefas em nível de aplicação - frequentemente abaixo da contagem de CPUs para I/O ou correspondendo a limites de conexão externos.
Canais quando produtores e consumidores são goroutines passando itens de trabalho.
Um mutex mais slice quando você precisa de prioridade indexada, inspeção ou lógica de drain de goroutine única.
Ele dá uma goroutine por requisição, não um pool para seu trabalho interno.
Você ainda limita chamadas paralelas ao banco de dados ou cliente HTTP dentro dos handlers.
Um consumidor lento causa um envio bloqueante em um canal ou uma falha em Acquire em um semáforo, o que retarda os produtores em vez de bufferizar trabalho ilimitado na memória.
errgroup retorna o primeiro erro e pode cancelar um contexto derivado.
WaitGroup apenas espera; o tratamento de erros permanece manual.
Não - use um contexto separado cancelado no shutdown do processo.
O contexto da requisição termina quando o cliente desconecta, o que é errado para logs de auditoria fire-and-forget, a menos que você explicitamente desvincule.
Inicie workers, cancele o contexto, feche as entradas, afirme que Wait() retorna dentro de um timeout e a contagem de goroutines retorna à linha de base.
Apenas quando o trabalho é independente e a capacidade downstream suporta N chamadas paralelas.
Caso contrário, fan-out amplifica throttling e erros.
runtime.NumGoroutine aumentando monotonicamente após a carga parar, mais contagens crescentes de heap e FDs abertos.
Sim - um estágio de pipeline pode usar errgroup internamente para passos paralelos dentro de um estágio.
Execute go test -race em pacotes com estado mutável compartilhado.
Ele não detecta deadlocks ou races lógicas em canais.
Versões de Stack: Esta página foi escrita para Go 1.26.x (padrão GC Green Tea, go fix modernizers - verifique o patch na compilação), chi (última versão - verifique na compilação), gin (última versão - verifique na compilação), echo (última versão - verifique na compilação), google.golang.org/grpc (última versão - verifique na compilação), sigs.k8s.io/controller-runtime (última versão - verifique na compilação), kubebuilder (última versão - verifique na compilação), tinygo (última versão - verifique os alvos de placa na compilação), wazero (última versão - verifique na compilação) e golangci-lint (última versão - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026