Concorrência Go: 9 padrões para apps escaláveis
Concorrência em Go vai além de goroutines. Conheça os 9 padrões essenciais para escrever código escalável, seguro e eficiente, com exemplos práticos de quando usar cada um.
Concorrência em Go vai além de goroutines. Conheça os 9 padrões essenciais para escrever código escalável, seguro e eficiente, com exemplos práticos de quando usar cada um.
Concorrência em Go é a arte de fazer muitas coisas ao mesmo tempo sem quebrar o programa. Diferente de paralelismo, que exige múltiplos núcleos, a concorrência organiza tarefas independentes que podem progredir em intervalos sobrepostos. Para aplicações escaláveis, dominar os padrões abaixo é mais importante do que decorar a sintaxe.
Os 9 padrões a seguir formam a caixa de ferramentas essencial de quem escreve sistemas concorrentes em Go. Eles aparecem em código de produção de empresas como Uber, Cloudflare e Docker.
1. Goroutines: a unidade básica
Goroutines são funções que executam concorrentemente, com custo inicial de apenas 2 KB de pilha. Para lançar uma, basta a palavra-chave go. Porém, sem sincronização, elas podem causar corridas de dados. Use goroutines para tarefas independentes, como processar requisições HTTP, mas sempre coordene o término com sync.WaitGroup.
2. Canais: comunicação segura entre goroutines
Canais são o mecanismo nativo de troca de dados. Eles evitam o compartilhamento de memória, seguindo o lema do Go: "não se comunique compartilhando memória; compartilhe memória se comunicando". Prefira canais quando a comunicação é o objetivo, como enviar resultados de uma goroutine para outra.
3. WaitGroup: esperar todas as goroutines
sync.WaitGroup é um contador que bloqueia a execução até que todas as goroutines sinalizem conclusão. É ideal para cenários onde você dispara N tarefas e precisa aguardar todas antes de prosseguir, como em um batch de envio de e-mails. Sem ele, o programa pode terminar antes das tarefas.
4. Mutex: proteger dados compartilhados
Quando múltiplas goroutines acessam a mesma variável, o sync.Mutex garante que apenas uma por vez modifique o dado. Use mutex para proteger contadores, mapas ou slices compartilhados. Em aplicações com alta contenção, considere sync.RWMutex para leituras simultâneas.
5. Pipeline: processamento em estágios
O padrão pipeline conecta canais em série: a saída de um estágio alimenta o próximo. Cada estágio roda em uma goroutine, processando dados em fluxo. É perfeito para transformações de dados, como ler de um arquivo, filtrar linhas e gravar em outro, sem carregar tudo na memória.
6. Fan-in e Fan-out: distribuir e consolidar trabalho
Fan-out distribui tarefas para várias goroutines; fan-in consolida os resultados em um único canal. Esse padrão acelera operações independentes, como baixar múltiplas URLs ou processar imagens. Combine com um worker pool para limitar o número de goroutines ativas.
7. Context: cancelamento e timeouts
O pacote context permite propagar cancelamento e prazos entre goroutines. Em aplicações web, cada requisição carrega um context que é cancelado se o cliente desconectar. Use context.WithTimeout para evitar que operações travem indefinidamente.
8. Select: lidar com múltiplos canais
select aguarda a primeira operação pronta entre vários canais. Ele é essencial para implementar timeouts, sinais de cancelamento e multiplexação. Por exemplo, você pode escutar um canal de dados e um canal de stop, encerrando a goroutine quando receber o sinal.
9. ErrGroup: propagar erros de goroutines
O pacote golang.org/x/sync/errgroup estende o WaitGroup, permitindo capturar o primeiro erro retornado por qualquer goroutine. Use-o para executar tarefas em paralelo e abortar as demais se uma falhar, como em chamadas a múltiplos serviços externos.
Qual padrão escolher?
Comece com goroutines e WaitGroup para tarefas simples. Se houver troca de dados, adote canais. Para proteger estado compartilhado, use mutex. Em pipelines de processamento, combine fan-out e fan-in. Sempre que houver risco de cancelamento, insira context. E para operações que podem falhar, prefira ErrGroup.
FAQ
O que é concorrência em Go?
Concorrência em Go é a capacidade de executar múltiplas tarefas simultaneamente, usando goroutines e canais. Segundo a Wikipedia, em ciência da computação, concorrência ocorre quando processos disputam acesso a recursos. Em Go, isso é feito de forma leve e segura.
Qual a diferença entre goroutine e thread?
Goroutines são mais leves que threads: começam com 2 KB de pilha e são gerenciadas pelo runtime do Go, enquanto threads do sistema operacional têm pilhas maiores e custo mais alto. Isso permite criar milhares de goroutines sem estourar memória.
Quando usar canais em vez de mutex?
Use canais quando a comunicação entre goroutines é o objetivo, como passar resultados. Use mutex quando precisa proteger um dado compartilhado, como um contador. Em geral, prefira canais para fluxo de dados e mutex para estado crítico.
O que é um worker pool em Go?
Worker pool é um padrão que limita o número de goroutines ativas, criando um grupo fixo de workers que consomem tarefas de um canal. Ele evita sobrecarga do sistema e é útil para controlar o uso de recursos.
Como evitar deadlock em Go?
Deadlock ocorre quando goroutines esperam umas pelas outras indefinidamente. Para evitar, sempre garanta que canais sejam fechados corretamente, use select com time.After para timeouts e evite locks aninhados.
Onde aprender mais sobre concorrência em Go?
A documentação oficial e o blog do Go têm artigos como "Share Memory By Communicating". Cursos como o da Alura e a playlist do canal "Aprenda Go" no YouTube oferecem exemplos práticos.