Trilha 09 · Concorrência e paralelismo

O problema dos filósofos e deadlock

Cinco filósofos sentam à mesa. Entre cada par há um garfo. Para comer, precisam de dois garfos. Se todos pegarem o garfo da esquerda ao mesmo tempo, ninguém consegue o da direita — deadlock. Esse problema clássico de Dijkstra (1965) captura a essência de qualquer deadlock: espera circular por recursos.

① Intuição

Deadlock = espera circular por recursos

Deadlock acontece quando um grupo de processos/threads fica bloqueado para sempre, cada um esperando por um recurso que outro do grupo segura. Ninguém libera, ninguém avança.

As quatro condições necessárias (Coffman, 1971): exclusão mútua (recurso só pode ser usado por um de cada vez), hold and wait (segura um recurso enquanto espera outro), sem preempção (recurso não pode ser tomado à força) e espera circular (ciclo de dependências).

Livelock: variante do deadlock onde os processos não ficam bloqueados mas continuam mudando de estado sem progredir — como duas pessoas num corredor estreito que ficam se desviando uma da outra sem conseguir passar. O sistema consome CPU mas não faz trabalho útil.
② Visualização interativa

Cinco filósofos à mesa

Veja o cenário sem deadlock (filósofos revezam) e o cenário de deadlock (todos pegam o garfo esquerdo simultaneamente).

mesaG0G1G2G3G4P0pensandoP1pensandoP2pensandoP3pensandoP4pensando
ESTADOS:
pensando
com fome
comendo
bloqueado
Todos pensando. Os 5 garfos estão livres.
1/4
③ Explicação técnica

As 4 condições de Coffman e como quebrá-las

# As 4 condições de Coffman (1971) — todas necessárias para deadlock:

# 1. Exclusão mútua: recursos não podem ser compartilhados
#    (garfo só pode estar nas mãos de 1 filósofo por vez)

# 2. Hold and wait: processo segura recursos e espera por mais
#    (filósofo segura garfo esquerdo e espera pelo direito)

# 3. Sem preempção: recurso não pode ser tomado à força
#    (garfo só é devolvido voluntariamente)

# 4. Espera circular: A espera por B, B espera por C, ... N espera por A
#    (P0 espera garfo do P1, P1 espera garfo do P2, ..., P4 espera garfo do P0)

# Prevenção: quebrar QUALQUER condição elimina o deadlock
# Estratégia 1: ordenação de recursos (quebra espera circular)
def comer_ordenado(eu, esq, dir_):
    primeiro  = min(esq, dir_)   # sempre pega o de menor ID primeiro
    segundo   = max(esq, dir_)
    with forks[primeiro]:       # P4 pegará fork0 antes de fork4
        with forks[segundo]:    # quebra a espera circular!
            eat(eu)

Solução: ordenação de recursos

# Solução completa: filósofos com ordenação de recursos
import threading

N = 5
forks = [threading.Lock() for _ in range(N)]

def philosopher(i):
    left  = i
    right = (i + 1) % N
    # Garante ordem global: pega o garfo de menor índice primeiro
    first, second = (left, right) if left < right else (right, left)

    while True:
        print(f"P{i} pensando")
        with forks[first]:      # menor índice primeiro — sem deadlock!
            with forks[second]:
                print(f"P{i} comendo")

threads = [threading.Thread(target=philosopher, args=(i,)) for i in range(N)]
for t in threads: t.start()

# Outras soluções:
# - Árbitro: 1 semáforo global, só N-1 filósofos podem tentar ao mesmo tempo
# - Chandy-Misra: filósofos pedem garfos com mensagens, sem locks compartilhados
Detecção vs Prevenção vs Evitação: Prevenção quebra uma das condições de Coffman (ordenação de recursos, timeout, etc.). Evitação usa o algoritmo do Banqueiro (Dijkstra) que aloca recursos apenas se o sistema permanece em estado seguro. Detecção permite deadlocks mas os detecta periodicamente e mata processos para resolver. Sistemas operacionais modernos geralmente usam detecção para deadlocks de IO e prevenção para locks de kernel.
④ Projeto para programar

Implementando os filósofos

Mini projeto: implemente os 5 filósofos em Python com threading.Lock. Primeiro sem proteção (deadlock provável). Depois adicione a solução de ordenação de recursos e confirme que nunca trava. Adicione logs com timestamps para visualizar a sequência de execução.

Projeto principal: implemente o algoritmo do Banqueiro de Dijkstra. Dado: N processos, M tipos de recurso, vetor de instâncias disponíveis, matriz de alocação e matriz de necessidade máxima. Implemente is_safe(state) que determina se o estado atual é seguro, e request(process, resource) que só aloca se o estado resultante for seguro.

Desafio extra: use a ferramenta de detecção de deadlock do Java (JConsole ou ThreadMXBean.findDeadlockedThreads()) para detectar automaticamente um deadlock que você criou. Depois implemente o mesmo em Python usando threading._active e análise do grafo de espera. Explique por que deadlocks em Python são mais raros que em Java (dica: GIL).

⑤ Exercícios rápidos

Teste sua intuição

Quantas das 4 condições de Coffman precisam ocorrer para haver deadlock?
A solução de ordenação de recursos quebra qual das condições de Coffman?
O que é um livelock e por que é diferente de deadlock?
⑥ Aplicações no mundo real

Onde você encontra isso

🛢️

Bancos de dados — detecção de deadlock

Bancos relacionais detectam deadlocks construindo um grafo de espera: se há um ciclo, há deadlock. PostgreSQL verifica a cada 1 segundo (lock_timeout). MySQL escolhe a transação de "menor custo" para abortar e liberar os locks. O DBA vê isso no log como "Deadlock found when trying to get lock; try restarting transaction". A solução é sempre a mesma: abortar uma das vítimas e fazer o cliente repetir.

☁️

Sistemas distribuídos — deadlock entre servidores

Em microsserviços, deadlocks podem ocorrer entre serviços que se chamam mutuamente com locks distribuídos (ex: Redis locks). Serviço A segura lock X e aguarda lock Y. Serviço B segura lock Y e aguarda lock X. A solução padrão é timeout + retry com jitter exponencial. O algoritmo de Chandy-Lamport detecta deadlocks distribuídos sem ponto centralizado.

🚀

Linux Kernel — lockdep

O Linux Kernel tem um subsistema chamado lockdep que valida a ordem de aquisição de locks em tempo de execução. Se detectar uma sequência que poderia causar deadlock (mesmo que não tenha causado ainda), reporta imediatamente. Lockdep mantém um grafo de todas as ordens de lock observadas e detecta ciclos potenciais. É ativado em builds de debug e encontrou centenas de bugs no kernel.

← Anterior: Mutex e semáforos Próxima: Async e concorrência →