Mutex e semáforos
O mutex (mutual exclusion) é a primitiva de sincronização mais fundamental: garante que apenas uma thread execute a seção crítica por vez. O semáforo generaliza isso para N threads simultâneas. Esses dois mecanismos são a base de toda sincronização em sistemas concorrentes.
O mutex como chave de banheiro
Um mutex é como a chave de um banheiro com porta trancável: somente quem tem a chave pode entrar. Se outra pessoa tentar, fica esperando do lado de fora. Quando a primeira pessoa sai, entrega a chave para quem estava esperando.
Um semáforo é como um estacionamento com N vagas: o sinal verde libera quando há vaga, o vermelho bloqueia quando está cheio. Quando um carro sai, o contador aumenta e o próximo carro pode entrar.
Mutex em ação: duas threads, uma seção crítica
Avance passo a passo e veja como T1 adquire o lock, T2 bloqueia, e a execução se alterna sem sobreposição na seção crítica.
Mutex em Python e C
# Python: threading.Lock — o mutex mais simples import threading lock = threading.Lock() counter = 0 def safe_increment(): global counter with lock: # adquire o lock, libera no final do bloco counter += 1 # seção crítica: só 1 thread aqui por vez # C com pthread (POSIX): // pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // pthread_mutex_lock(&mutex); ← bloqueia se já travado // counter++; ← seção crítica // pthread_mutex_unlock(&mutex); ← libera, acorda quem espera # RLock (reentrant): permite que a MESMA thread trave novamente rlock = threading.RLock() with rlock: with rlock: # não bloqueia! mesma thread pode reentrá-lo pass # útil em recursão com lock
Semáforo: até N threads simultâneas
# Semáforo: generalização do mutex # Mutex = semáforo com valor máximo 1 # Semáforo: permite N threads simultaneamente na seção crítica import threading sem = threading.Semaphore(3) # máx 3 threads simultâneas def acessar_recurso(i): with sem: # decrementa o contador interno; bloqueia se == 0 print(f"Thread {i} acessando recurso") time.sleep(1) # simula uso do recurso # ao sair: incrementa o contador, acorda uma thread esperando # Uso clássico: pool de conexões ao banco de dados # Exemplo: máx 10 conexões simultâneas db_pool = threading.Semaphore(10) def query(sql): with db_pool: # garante ≤ 10 queries simultâneas conn = get_connection() conn.execute(sql) release_connection(conn) # Produtor/Consumidor com semáforos: # empty = Semaphore(N) ← slots vazios no buffer # full = Semaphore(0) ← slots cheios no buffer # Produtor: down(empty), put(item), up(full) # Consumidor: down(full), get(item), up(empty)
threading.Lock
em CPython) garantem FIFO para evitar starvation. Locks de alta performance (spinlocks)
não têm essa garantia.
Sincronizando acesso a recursos
Mini projeto: implemente um pool de conexões com semáforo: simule 5 conexões de banco disponíveis e 20 threads tentando usá-las. Sem semáforo, print o número de conexões ativas e observe que pode ultrapassar 5. Com Semaphore(5), garanta que nunca excedem 5 simultâneas.
Projeto principal: implemente o problema do produtor/consumidor com buffer circular de tamanho N. Use dois semáforos (empty e full) e um mutex para proteger o buffer. Teste com 3 produtores e 2 consumidores. Garanta que não há race condition, deadlock ou starvation.
Desafio extra: implemente um readers-writers lock (RWLock): múltiplos leitores simultâneos são permitidos, mas um escritor precisa de acesso exclusivo. Implemente usando apenas mutexes e semáforos simples. Garanta que escritores não sofrem starvation quando há muitos leitores contínuos.
Teste sua intuição
Onde você encontra isso
Linux Kernel — spinlock vs mutex
O kernel Linux usa dois tipos de lock: spinlock (a thread fica em busy-wait, consumindo CPU — adequado para seções críticas muito curtas em código de interrupção) e mutex (a thread dorme e é acordada — adequado para esperas longas). Escolher o tipo errado pode causar deadlock (mutex em contexto de interrupção) ou desperdiçar CPU (spinlock por muito tempo).
PostgreSQL — MVCC e locks
O PostgreSQL usa MVCC (Multi-Version Concurrency Control) para leituras sem bloqueio: cada transação vê um snapshot consistente do banco sem precisar de lock de leitura. Locks só são necessários para escritas conflitantes. O sistema de lock do Postgres tem 8 níveis de granularidade — de ACCESS SHARE (leitura) a ACCESS EXCLUSIVE (DDL) — com detecção automática de deadlock.
Rust — sincronização sem race conditions
O Rust proíbe race conditions em tempo de compilação via ownership e o sistema de tipos. Mutex<T> envolve um valor T — para acessá-lo você precisa adquirir o lock, que retorna um MutexGuard. Quando o guard sai de escopo, o lock é liberado automaticamente. Se você tentar acessar T sem o lock, o compilador rejeita. Zero race conditions garantidas sem overhead de runtime.