Trilha 09 · Concorrência e paralelismo

Threads e processos

Todo programa em execução é um processo com seu próprio espaço de memória. Threads são "linhas de execução" dentro do mesmo processo — compartilham código, heap e dados globais, mas cada uma tem sua própria stack e registradores. Essa memória compartilhada é ao mesmo tempo a maior vantagem e o maior risco das threads.

① Intuição

Processo = apartamento. Thread = morador.

Um processo é como um apartamento: tem seu próprio endereço, móveis (heap) e não pode acessar diretamente o apartamento do vizinho. Uma thread é como um morador dentro desse apartamento — pode usar todos os cômodos (memória compartilhada), mas se dois moradores tentarem modificar a mesma coisa ao mesmo tempo, causam conflito.

Processos têm isolamento mas comunicação cara (IPC). Threads têm comunicação barata (memória compartilhada) mas exigem sincronização.

Concorrência vs Paralelismo: concorrência é lidar com várias tarefas ao mesmo tempo (um núcleo alternando entre threads). Paralelismo é executar várias tarefas literalmente ao mesmo tempo (múltiplos núcleos). Com 1 núcleo você pode ter concorrência sem paralelismo; com N núcleos e N threads você tem ambos.
② Visualização interativa

Modelo de memória: processo com múltiplas threads

Selecione o número de threads. Veja como elas compartilham código, dados e heap, mas cada uma tem sua stack privada.

número de threads:concorrente
PROCESSO — espaço de endereçamento
Código
só leitura
← compartilhado →
Dados globais
BSS + data
← compartilhado →
Heap
malloc / new
← compartilhado →
Stack e registradores — privados por thread:
Thread 0
frame local()
args + locals
PC / SP / reg
Thread 1
frame local()
args + locals
PC / SP / reg
2 threads: compartilham código, dados globais e heap — qualquer escrita nessas áreas precisa de sincronização.
③ Explicação técnica

Criando threads em Python

# Python: criando threads com threading.Thread
import threading

counter = 0   # variável global compartilhada — PERIGOSO!

def incrementar():
    global counter
    for _ in range(1_000_000):
        counter += 1   # NÃO é atômico → race condition

t1 = threading.Thread(target=incrementar)
t2 = threading.Thread(target=incrementar)
t1.start(); t2.start()
t1.join();  t2.join()
print(counter)   # provavelmente < 2.000.000 — race condition!

# Processo vs Thread:
# Processo: espaço de endereçamento próprio, isolado
# Thread: execução dentro de um processo, compartilha memória
# fork() cria processo filho (cópia do pai)
# pthread_create() / Thread() cria thread no mesmo processo

Processo vs Thread — fork() vs pthread_create()

// C: diferença entre processo e thread na prática

// Criar processo (Unix) — espaço de endereçamento separado
pid_t pid = fork();
if (pid == 0) {
    // Processo filho: cópia independente do pai
    // Modificar variável aqui NÃO afeta o pai (COW)
}

// Criar thread (POSIX) — compartilha memória com o criador
pthread_t t;
pthread_create(&t, NULL, thread_func, NULL);
// thread_func rodando em paralelo agora
// Acesso a variáveis globais/heap é compartilhado → sincronize!
pthread_join(t, NULL);  // espera thread terminar

// Python: GIL (Global Interpreter Lock)
// O CPython tem um lock global que impede que 2 threads Python
// rodem bytecode simultaneamente. Threads são úteis para I/O
// mas NÃO para CPU-bound paralelo. Use multiprocessing para isso.
O GIL do Python: o CPython tem um Global Interpreter Lock que impede duas threads de executar bytecode Python simultaneamente. Isso elimina muitas race conditions automaticamente — mas também elimina o paralelismo para código CPU-bound. Threads Python são úteis para I/O concorrente (rede, disco), não para cálculos paralelos. Para CPU-bound, use multiprocessing (processos separados) ou C extensions que liberam o GIL.
④ Projeto para programar

Explorando threads na prática

Mini projeto: reproduza a race condition do contador em Python: crie 2 threads que cada uma incrementa uma variável global 1.000.000 vezes. Execute várias vezes e observe valores diferentes. Depois use threading.Lock para corrigir — e verifique que o resultado é sempre 2.000.000.

Projeto principal: implemente um web scraper concorrente que busca o título HTML de 20 URLs em paralelo usando threading.Thread. Compare o tempo com a versão sequencial. Meça também com concurrent.futures.ThreadPoolExecutor. Por que threads ajudam aqui mesmo com o GIL?

Desafio extra: em C, crie um programa com 4 threads que calculam a soma de partes diferentes de um array de 100 milhões de inteiros (paralelismo de dados). Use pthread_create e pthread_join. Compare com a versão single-thread. Calcule o speedup real e compare com o previsto pela Lei de Amdahl (lição 9.6).

⑤ Exercícios rápidos

Teste sua intuição

Qual é a diferença fundamental entre processo e thread em termos de memória?
Por que threads Python não ajudam para operações CPU-bound?
Qual é a diferença de custo entre criar um processo (fork) e criar uma thread?
⑥ Aplicações no mundo real

Onde você encontra isso

🌐

Servidores web — thread per request

Apache (modo prefork) cria um processo por requisição; Apache MPM worker e Nginx usam threads. Node.js usa um único thread com event loop. Go usa goroutines (threads ultra-leves, ~2KB de stack). Cada modelo tem tradeoffs: thread-per-request é simples mas usa muita memória; event loop é eficiente para I/O mas bloqueia no CPU.

🎮

Jogos — threads por subsistema

Engines de jogos modernos (Unreal, Unity, Godot) usam múltiplas threads: thread de renderização, thread de física, thread de áudio, thread de rede. Cada subsistema roda independentemente. A comunicação entre threads é feita com filas de mensagens para evitar race conditions. O Unreal Engine 5 usa um sistema de "task graph" com centenas de tarefas paralelas por frame.

🗄️

Bancos de dados — isolamento de transações

PostgreSQL cria um processo por conexão (não thread) para isolamento total. MySQL usa threads. SQLite permite apenas uma thread escrevendo por vez (WAL mode melhora isso). Sistemas como Spanner e CockroachDB usam concorrência massiva com protocolos de consenso (Paxos, Raft) para garantir consistência entre servidores.

← Trilha: Concorrência e paralelismo Próxima: Race conditions →