Padrões de concorrência: 9 modelos para sistemas distribuídos
Padrões de concorrência organizam como threads, processos e serviços compartilham recursos sem travar o sistema. O guia cobre 9 modelos, do mutex ao ator, com critérios práticos de escolha e ressalvas de implementação.
Padrões de concorrência organizam como threads, processos e serviços compartilham recursos sem travar o sistema. O guia cobre 9 modelos, do mutex ao ator, com critérios práticos de escolha e ressalvas de implementação.
Padrões de concorrência são soluções reutilizáveis para coordenar acesso a recursos compartilhados e comunicação entre tarefas. Em sistemas distribuídos, eles definem como threads, processos e serviços se sincronizam sem travar o conjunto. Abaixo, nove padrões ordenados por relevância prática, com critério de escolha e ressalva concreta.
1. Exclusão mútua (mutex)
Garante que apenas uma tarefa acesse uma seção crítica por vez. É o padrão mais elementar e ainda o mais usado em bibliotecas de baixo nível. Em Go, o sync.Mutex expõe esse contrato diretamente; em Java, o bloco synchronized faz o mesmo. O critério é simples: use quando o recurso é único e o tempo de posse é curto. Ressalva: em sistema distribuído, um mutex local não coordena nós distintos, exige consenso à parte.
2. Semáforo
Generaliza o mutex permitindo N acessos simultâneos. Um pool de conexões com limite de 10 é um semáforo de contagem. O critério mensurável é a razão entre recurso disponível e demanda: se há 10 conexões e 200 requisições por segundo, o semáforo evita esgotamento. Cuidado com o inverso: limite baixo demais vira gargalo e fila crescente.
3. Monitor
Combina exclusão mútua com variáveis de condição, permitindo que uma tarefa espere por um estado específico. É a base de estruturas como BlockingQueue em Java. O critério é a presença de espera condicional: se a tarefa precisa aguardar "até haver item", monitor é a escolha. Em sistemas distribuídos, o equivalente exige coordenação externa, como um lock distribuído.
4. Canal
Transfere dados entre tarefas sem memória compartilhada. Em Go, canais são o mecanismo idiomático; em Rust, mpsc cumpre o papel. O critério é o desacoplamento: produtor e consumidor não precisam conhecer um ao outro. A ressalva é o bloqueio implícito: canal sem buffer trava o emissor até o receptor consumir.
5. Pool de threads
Reutiliza um conjunto fixo de threads para evitar o custo de criação por tarefa. O critério é a taxa de chegada: se cada requisição cria uma thread, o overhead domina acima de algumas centenas por segundo. Um pool de 4 a 8 threads por núcleo costuma bastar para cargas mistas. O risco é a fila ilimitada mascarar saturação.
6. Futures e promises
Representam o resultado de uma operação assíncrona que ainda não terminou. O critério é a composição: encadear then ou await evita callback hell. Em sistemas distribuídos, o padrão aparece em chamadas RPC assíncronas. Ressalva: exceções não tratadas em futures podem sumir silenciosamente.
7. Ator
Cada ator processa mensagens sequencialmente e mantém estado isolado. Erlang e Akka são referências. O critério é a localidade de estado: se cada entidade tem seu próprio estado e recebe comandos, o modelo de ator reduz locks. O custo é o diagnóstico: falhas em cadeia de mensagens são mais difíceis de rastrear.
8. Reativo
Propaga mudanças de forma assíncrona, com contrapressão. O critério é o fluxo contínuo: streams de eventos com volumes variáveis se beneficiam. A ressalva é a complexidade: depurar um pipeline reativo exige ferramentas específicas.
9. Produtor-consumidor
Desacopla quem gera de quem processa, com buffer intermediário. É a base de filas como Kafka e RabbitMQ. O critério é a diferença de ritmo: se o produtor é mais rápido que o consumidor, o buffer absorve picos. O risco é o crescimento ilimitado da fila.
Qual escolher
Comece pelo mais simples que resolva: mutex ou semáforo para recursos locais; canal ou ator quando o estado é distribuído por entidade; futures para operações assíncronas isoladas. Em sistemas distribuídos, lembre que nenhum desses padrões substitui consenso (Raft, Paxos) para decisões globais.
FAQ
O que são padrões de concorrência?
São soluções reutilizáveis para coordenar acesso a recursos compartilhados e comunicação entre tarefas. Eles descrevem contratos testados, como exclusão mútua, canais e atores, que reduzem erros comuns em programação concorrente.
Qual a diferença entre mutex e semáforo?
Mutex permite um acesso por vez; semáforo permite N acessos simultâneos. O mutex é um caso particular do semáforo com contador 1. Use mutex para seção crítica única e semáforo para limitar pools de recursos.
Quando usar o padrão ator?
Quando cada entidade tem estado próprio e recebe comandos. O ator processa mensagens sequencialmente, evitando locks explícitos. É indicado para sistemas com muitas entidades independentes, como chats e jogos.
Padrões de concorrência resolvem problemas em sistemas distribuídos?
Parcialmente. Eles coordenam tarefas dentro de um nó. Para decisões globais entre nós, é preciso consenso distribuído, como Raft ou Paxos. Os padrões locais não substituem esses algoritmos.
O que é contrapressão no padrão reativo?
É o mecanismo que impede o produtor de sobrecarregar o consumidor. Quando o consumidor fica lento, ele sinaliza ao produtor para reduzir o ritmo, evitando filas ilimitadas e esgotamento de memória.
Qual padrão escolher para filas de mensagens?
Produtor-consumidor com buffer. Ele desacopla ritmos diferentes e absorve picos. Ferramentas como Kafka e RabbitMQ implementam esse padrão. Monitore o tamanho da fila para evitar crescimento indefinido.