🧠FFVAcademy
🔀

Threads vs processos vs fibras: modelo de concorrência

15 min de leitura·+75 XP

Concorrência pode ser implementada de formas radicalmente diferentes: threads do SO, processos, goroutines e event loops. Cada modelo tem trade-offs de custo de criação, overhead de comunicação e complexidade de sincronização.

Processo vs Thread: o que o kernel vê

AspectoProcessoThread (mesm. processo)
MemóriaEspaço virtual próprioCompartilhada com todos do processo
File descriptorsPróprios (herdados no fork)Compartilhados
Criação (Linux)fork() ~1mspthread_create() ~0.1ms
Context switch3-10µs (TLB flush)1-2µs (mesma tabela de páginas)
ComunicaçãoIPC (pipe, socket, shm)Memória compartilhada direta
IsolamentoTotal — crash não afeta outrosPartial — crash mata todos os threads
GIL PythonNão compartilhado (cada processo tem o seu)Compartilhado (um thread por vez)

Green threads e M:N threading

# Comparação de custo de criação e context switch:

import threading, time, asyncio

# Thread do SO — ~0.1ms para criar, ~1-2µs para context switch
def tarefa_thread():
    time.sleep(0.001)    # bloqueia thread

inicio = time.time()
threads = [threading.Thread(target=tarefa_thread) for _ in range(1000)]
for t in threads: t.start()
for t in threads: t.join()
print(f"1000 threads: {time.time()-inicio:.2f}s")   # ~1-2s de overhead

# Coroutine asyncio — ~0µs para criar, zero context switch de kernel
async def tarefa_async():
    await asyncio.sleep(0.001)    # cede controle, não bloqueia

async def main():
    inicio = time.time()
    await asyncio.gather(*[tarefa_async() for _ in range(10_000)])
    print(f"10000 coroutines: {time.time()-inicio:.2f}s")   # ~0.01s!

asyncio.run(main())

# Go goroutines (conceito — Python não tem equivalente nativo):
# go minha_funcao()   # cria goroutine com 2KB de stack
# São M:N: N goroutines mapeadas para M threads do OS
# runtime.GOMAXPROCS(4) = 4 threads do OS para N goroutines
# Go scheduler: work-stealing, preemptivo, cooperativo em I/O

# Erlang/Elixir processes (similar a goroutines mas com isolamento de memória):
# spawn(fn -> ... end)  # processo Erlang leve, ~300 bytes
# Comunicação por message passing — sem memória compartilhada
# Crash de um processo não afeta outros (fault isolation)

Escolhendo o modelo de concorrência

# DECISION TREE:
#
# Preciso de paralelismo CPU-bound?
#   └── Sim → multiprocessing.Process ou ProcessPoolExecutor
#              (cada processo tem seu GIL em Python)
#
# Preciso de I/O concorrente (muitas conexões/requests)?
#   ├── Código existente síncrono → ThreadPoolExecutor
#   └── Código novo → asyncio + httpx/aiohttp/asyncpg
#
# Muitas conexões leves (>1000)?
#   └── asyncio (event loop sobre epoll)
#
# Comunicação entre workers sem overhead de pickle/IPC?
#   └── threads (memória compartilhada) — cuidado com race conditions
#
# Isolamento de falhas crítico?
#   └── processos separados (crash de um não afeta outros)

# Python multiprocessing (processos reais):
from multiprocessing import Process

def worker(n):
    print(f"Worker {n}, PID: {os.getpid()}")
    # Memória isolada — crash aqui não afeta main

processos = [Process(target=worker, args=(i,)) for i in range(4)]
for p in processos: p.start()
for p in processos: p.join()

# Verificar threads de um processo (em Linux):
# ps -eLf | grep python
# cat /proc/$(pgrep python)/status | grep Threads
Resumo prático: threads para I/O concorrente com código síncrono. asyncio para I/O concorrente com código novo. multiprocessing para paralelismo CPU real. Goroutines/Erlang processes para sistemas com muita concorrência leve e isolamento de falhas. O GIL Python torna threads inadequadas para CPU-bound — use multiprocessing.
💡
Próximo: Containers por baixo — namespaces e cgroups do Linux que o Docker usa.
🧩

Quiz rápido

3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito

Continue lendo