Async, event loop e concorrência cooperativa
Threads não são a única forma de concorrência. O modelo assíncrono usa um único thread com um event loop: quando uma operação de I/O começa, a tarefa suspende e outra roda. Sem locks, sem race conditions — mas com uma pegadinha: código CPU-bound bloqueia tudo.
Um garçom atendendo múltiplas mesas
O modelo de threads é como ter N garçons (um por mesa): trabalham em paralelo mas precisam coordenar o uso da cozinha (mutex). O modelo assíncrono é como um garçon eficiente: anota o pedido (inicia I/O), vai atender outra mesa enquanto a cozinha prepara, e volta quando está pronto. Um garçon, N mesas — concorrência sem paralelismo.
async/await é "concorrência cooperativa": a tarefa voluntariamente
cede controle ao event loop quando precisa esperar. O event loop escolhe a próxima
tarefa pronta. Não há preempção involuntária — o código entre dois await
roda sem interrupção.
O event loop do JavaScript passo a passo
Veja como o código síncrono, Promises (microtasks) e setTimeout (macrotasks) são processados em ordens diferentes pelo event loop.
async/await em Python
# Python async/await — concorrência cooperativa sem threads import asyncio async def fetch_url(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text() async def main(): urls = ["https://api.site.com/1", "https://api.site.com/2", ...] # gather: executa todas as coroutines concorrentemente results = await asyncio.gather(*[fetch_url(u) for u in urls]) return results # await suspende esta coroutine enquanto espera I/O # O event loop roda outra coroutine enquanto espera # Tudo em 1 thread — sem race conditions! # Mas: uma operação CPU-bound bloqueia TUDO asyncio.run(main()) # Python 3.7+
Event loop do JavaScript e goroutines do Go
// JavaScript: o event loop é a base de tudo no Node.js console.log('A'); // 1. síncrono — executa imediato setTimeout(() => { // 2. macrotask: timer API console.log('B'); // cb_B → Task Queue após 0ms }, 0); Promise.resolve().then(() => { // 3. microtask: promise console.log('C'); // cb_C → Microtask Queue (imediato) }); console.log('D'); // 4. síncrono // Saída: A D C B // Ordem: síncrono → microtasks → (próxima iteração) macrotasks // Promessas têm prioridade sobre setTimeout! // Go: goroutines — threads leves do runtime Go go func() { fmt.Println("goroutine rodando") // thread leve (2KB stack inicial) }() // Runtime Go gerencia milhares de goroutines em poucos OS threads // Comunicação via canais (channels), não memória compartilhada
.then() pode bloquear a
renderização — é o "starvation de microtasks".
Construindo servidores assíncronos
Mini projeto: use asyncio para fazer 10 requisições HTTP em paralelo para a API pública jsonplaceholder.typicode.com. Compare o tempo com a versão sequencial (requests simples). Adicione um semáforo assíncrono (asyncio.Semaphore(3)) para limitar a 3 requisições simultâneas.
Projeto principal: implemente um servidor HTTP simples com asyncio que serve arquivos estáticos. Use asyncio.start_server. Simule 1000 conexões simultâneas com asyncio.gather. Compare com um servidor threading que cria uma thread por conexão — memória usada, latência, throughput.
Desafio extra: implemente um chat server em Go usando goroutines e channels: cada cliente tem uma goroutine para leitura e uma para escrita; um "hub" goroutine gerencia broadcast com um canal. Compare com a implementação em Python asyncio. Explique a diferença entre comunicação via channels (CSP) e memória compartilhada com locks.
Teste sua intuição
Onde você encontra isso
Node.js — 1 thread, milhões de conexões
O Node.js usa um único thread com event loop (libuv). Isso é suficiente para servir dezenas de milhares de conexões simultâneas porque a maioria espera I/O (rede, banco, disco). A crítica histórica era que código CPU-bound bloqueava tudo — resolvido com Worker Threads (Node 12+) e wasm para cálculos pesados. Discord usa Node.js e serve milhões de usuários simultâneos.
FastAPI — async Python moderno
FastAPI (Python) usa asyncio e Starlette para servir requisições HTTP assíncronas. Com uvicorn (servidor ASGI), um único processo serve centenas de requisições simultâneas sem threads. Benchmark: FastAPI supera Flask em throughput porque evita o overhead de criar threads por requisição. É o framework Python mais rápido para APIs, usado por Netflix, Microsoft e Uber.
Tokio — async runtime para Rust
Tokio é o runtime assíncrono mais popular do Rust: implementa o event loop, agendador de tarefas e I/O não-bloqueante. Programas Rust com Tokio usam zero threads do SO para concorrência de I/O — tudo é gerenciado pelo runtime em poucos threads. O servidor web Axum sobre Tokio é um dos mais rápidos em benchmarks TechEmpower, competindo com C e Go.