Escolhendo um Modelo de Concorrência
Escolha asyncio, threads ou processos com base no formato da carga de trabalho, APIs de bibliotecas e operabilidade - não em tendências. Este checklist guia desde os dados de perfilamento até uma escolha defensável.
Como Usar Este Checklist
- Perfilar primeiro (
py-spy,cProfile, APM) - rótulos mentem. - Documentar a escolha em um ADR quando o serviço for de longa duração.
- Revisitar após mudanças nas dependências (SDK síncrono para assíncrono).
Classificação da Carga de Trabalho
- Medir espera dominante: Se >60% do tempo espera por I/O, tratar como I/O-bound; se CPU satura os núcleos, CPU-bound.
- Formato da API da biblioteca: Cliente async nativo -> asyncio; bloqueante apenas -> threads ou
to_thread. - Necessidade de paralelismo: Muitos sockets concorrentes -> asyncio; poucas chamadas bloqueantes -> pequeno pool de threads.
- Núcleos de CPU: Loops de CPU em Python ->
ProcessPoolExecutor; extensões NumPy/C podem liberar GIL em threads. - Isolamento: Código nativo propenso a falhas -> processos; cache compartilhado em memória -> threads/async com cuidado.
Tabela de Decisão do Modelo
| Perfil | Primeira escolha | Alternativa |
|---|---|---|
| Muitas esperas HTTP/DB, bibliotecas async | asyncio | ThreadPool + bibliotecas bloqueantes |
| SDK bloqueante, concorrência moderada | ThreadPoolExecutor | asyncio + to_thread |
| Python puro de CPU | ProcessPoolExecutor | Extensão C / vetorizar |
| Estágio misto de API + CPU | asyncio + pool de processos | Divisão de pool de threads + pool de processos |
| Script simples, <5 tarefas | Sequencial | threads se I/O for lento |
Fluxo de Trabalho Passo a Passo
Passo 1: A tarefa é I/O-bound?
Se sim e existirem bibliotecas com capacidade async, prefira asyncio para centenas+ de esperas concorrentes. Se as bibliotecas forem bloqueantes, use ThreadPoolExecutor dimensionado para os limites de conexão.
Passo 2: A tarefa é CPU-bound em bytecode Python?
Se sim na build padrão com GIL, use multiprocessing ou ProcessPoolExecutor. Não espere aceleração de threads para loops apertados.
Passo 3: A carga de trabalho é mista?
Divida o pipeline: busca async -> asyncio.to_thread ou run_in_executor(ProcessPool) para transformação. Mantenha o trabalho de CPU fora da thread do loop de eventos.
Passo 4: Quais são as restrições de operabilidade?
O spawn no Windows requer workers serializáveis (picklable); threads daemon complicam o desligamento gracioso; memória de processo = baseline × workers. Considere isso na escolha.
Passo 5: Você mediu após a implementação?
Compare latência p50/p99 e CPU sob carga realista. O modelo errado se manifesta como bloqueio do loop de eventos, contenção do GIL ou thrash de spawn de processos.
FAQs
Padrão para serviços FastAPI?
Asyncio - o framework é nativo async; descarregue o bloqueante para o pool de threads com moderação.
Quando o sequencial é aceitável?
Scripts de administração de baixo QPS, cron com conexão única, protótipos antes da prova de escala.
Posso misturar threads e asyncio?
Sim, via executor - nunca chame I/O bloqueante diretamente na thread do loop.
E quanto ao gevent?
Modelo legado de monkey-patch - prefira asyncio para projetos novos, a menos que esteja preso a uma stack síncrona.
Quantos workers no pool de threads?
Tamanho do pool de conexões ou chamadas bloqueantes concorrentes - não a contagem de CPU.
Pool de processos em tarefas pequenas?
Evite - a tarefa deve amortizar o custo de spawn/pickle.
Polars/pandas mudam a escolha?
Operações pesadas rodam em código nativo - frequentemente liberam o GIL; ainda assim, perfilar, pode permanecer em threads no mesmo processo.
Impacto do Python com threads livres (free-threaded)?
Pode mudar a escolha de threads para CPU-bound - reexecute o checklist após auditoria de extensões.
Workers Redis/Celery?
Workers baseados em processos são uma escolha separada - frequentemente multiprocessing por design para isolamento.
Documentar onde?
ADR: carga de trabalho, perfil medido, modelo escolhido, alternativas rejeitadas, gatilho de revisão.
Relacionado
- Noções Básicas de Concorrência - terminologia
- O GIL e o Python com Threads Livres - threads de CPU
- Noções Básicas de Asyncio - caminho async
- Melhores Práticas de Concorrência - regras de segurança
Versões da Stack: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.