Mutex<T> e RwLock<T> em Rust

Guia completo de Mutex e RwLock em Rust: lock, try_lock, poisoning, MutexGuard, RwLock, Arc+Mutex com exemplos práticos em português.

O que faz e quando usar

Mutex<T> (mutual exclusion) e RwLock<T> (read-write lock) são primitivas de sincronização que protegem dados compartilhados entre threads. Eles garantem que apenas uma thread por vez (no caso do Mutex) ou múltiplas leitoras/uma escritora (no caso do RwLock) acessem o dado protegido.

Use Mutex<T> quando:

  • Múltiplas threads precisam ler e escrever o mesmo dado.
  • O acesso é predominantemente de escrita ou o tempo de lock é curto.
  • Você quer a primitiva mais simples e direta.

Use RwLock<T> quando:

  • muitas leituras e poucas escritas (leitores não bloqueiam outros leitores).
  • O tempo de leitura é significativo e você quer maximizar o paralelismo de leitura.

Em ambos os casos, combine com Arc<T> para compartilhar entre threads.


Tipos e Funções Principais

Mutex

ItemDescrição
Mutex::new(value)Cria um novo Mutex contendo value
mutex.lock()Adquire o lock, retorna LockResult<MutexGuard<T>>
mutex.try_lock()Tenta adquirir sem bloquear, retorna TryLockResult
mutex.is_poisoned()Verifica se o Mutex foi envenenado
mutex.into_inner()Consome o Mutex e retorna o valor interno
MutexGuard<T>RAII guard — libera o lock automaticamente no drop

RwLock

ItemDescrição
RwLock::new(value)Cria um novo RwLock contendo value
rwlock.read()Adquire lock de leitura (múltiplos simultâneos)
rwlock.write()Adquire lock de escrita (exclusivo)
rwlock.try_read()Tenta lock de leitura sem bloquear
rwlock.try_write()Tenta lock de escrita sem bloquear

Exemplos de Código

Mutex básico

use std::sync::Mutex;

fn main() {
    let contador = Mutex::new(0);

    // Adquirir o lock
    {
        let mut guard = contador.lock().unwrap();
        *guard += 1;
        println!("Dentro do lock: {}", *guard);
    } // guard é dropado aqui -> lock liberado automaticamente

    // Acessar o valor novamente
    println!("Valor final: {}", *contador.lock().unwrap());
}

Arc<Mutex> — padrão para múltiplas threads

  +--------+    +--------+    +--------+
  | Thread |    | Thread |    | Thread |
  |   0    |    |   1    |    |   2    |
  +---+----+    +---+----+    +---+----+
      |             |             |
      +------+------+------+------+
             |             |
          Arc::clone    Arc::clone
             |             |
        +----+-------------+----+
        |   Arc<Mutex<Dados>>   |
        |   +----------------+  |
        |   | Mutex<Dados>   |  |
        |   | +------------+ |  |
        |   | | Dados      | |  |
        |   | +------------+ |  |
        |   +----------------+  |
        +-----------------------+
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let contador = Arc::new(Mutex::new(0u64));
    let mut handles = vec![];

    for id in 0..10 {
        let contador = Arc::clone(&contador);
        let handle = thread::spawn(move || {
            for _ in 0..1_000 {
                let mut num = contador.lock().unwrap();
                *num += 1;
            }
            println!("Thread {} terminou", id);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    // Sempre será 10.000 — sem data races!
    println!("Valor final: {}", *contador.lock().unwrap());
}

try_lock — tentativa sem bloqueio

use std::sync::Mutex;

fn main() {
    let dados = Mutex::new(vec![1, 2, 3]);

    // Primeiro lock (sucesso)
    let guard = dados.lock().unwrap();
    println!("Lock adquirido: {:?}", *guard);

    // Tentar um segundo lock na mesma thread -> falharia (deadlock)
    match dados.try_lock() {
        Ok(g) => println!("Segundo lock: {:?}", *g),
        Err(_) => println!("Não foi possível adquirir o lock (já bloqueado)"),
    }

    // Liberar o primeiro lock
    drop(guard);

    // Agora try_lock funciona
    match dados.try_lock() {
        Ok(g) => println!("Lock adquirido após drop: {:?}", *g),
        Err(_) => println!("Ainda bloqueado"),
    }
}

Poisoning — Mutex envenenado

Quando uma thread entra em pânico enquanto segura um MutexGuard, o Mutex fica “envenenado” (poisoned). Isso alerta as outras threads de que os dados podem estar em um estado inconsistente:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let dados = Arc::new(Mutex::new(vec![1, 2, 3]));

    // Thread que vai entrar em pânico com o lock
    let dados_clone = Arc::clone(&dados);
    let handle = thread::spawn(move || {
        let mut guard = dados_clone.lock().unwrap();
        guard.push(4);
        panic!("Ops! Algo deu errado enquanto modificava os dados");
    });

    // Aguardar a thread (que vai dar panic)
    let _ = handle.join();

    // Tentar acessar o Mutex envenenado
    match dados.lock() {
        Ok(guard) => println!("Dados: {:?}", *guard),
        Err(envenenado) => {
            eprintln!("Mutex envenenado! Recuperando dados...");
            // Podemos recuperar o guard mesmo envenenado
            let guard = envenenado.into_inner();
            println!("Dados recuperados: {:?}", *guard);
            // Dados podem estar em estado inconsistente!
        }
    }
}

RwLock — múltiplos leitores, um escritor

use std::sync::{Arc, RwLock};
use std::thread;
use std::time::Duration;

fn main() {
    let config = Arc::new(RwLock::new(Config {
        max_conexoes: 100,
        timeout_ms: 5000,
        modo_debug: false,
    }));

    let mut handles = vec![];

    // 5 threads leitoras (executam em paralelo entre si)
    for id in 0..5 {
        let config = Arc::clone(&config);
        handles.push(thread::spawn(move || {
            for _ in 0..3 {
                let cfg = config.read().unwrap();
                println!(
                    "Leitor {}: max_conexoes={}, timeout={}ms",
                    id, cfg.max_conexoes, cfg.timeout_ms
                );
                // Múltiplos leitores podem ler simultaneamente
                thread::sleep(Duration::from_millis(50));
            }
        }));
    }

    // 1 thread escritora (bloqueia leitores durante a escrita)
    {
        let config = Arc::clone(&config);
        handles.push(thread::spawn(move || {
            thread::sleep(Duration::from_millis(100));
            let mut cfg = config.write().unwrap();
            cfg.max_conexoes = 200;
            cfg.modo_debug = true;
            println!("Escritor: config atualizada!");
        }));
    }

    for handle in handles {
        handle.join().unwrap();
    }

    let cfg = config.read().unwrap();
    println!(
        "Config final: max={}, debug={}",
        cfg.max_conexoes, cfg.modo_debug
    );
}

struct Config {
    max_conexoes: u32,
    timeout_ms: u64,
    modo_debug: bool,
}

Cache thread-safe com RwLock

use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::thread;

type Cache = Arc<RwLock<HashMap<String, String>>>;

fn buscar(cache: &Cache, chave: &str) -> String {
    // Tenta ler sem lock exclusivo
    {
        let leitura = cache.read().unwrap();
        if let Some(valor) = leitura.get(chave) {
            return valor.clone(); // cache hit
        }
    } // lock de leitura liberado

    // Cache miss — adquirir lock de escrita
    let valor = format!("resultado_para_{}", chave);
    {
        let mut escrita = cache.write().unwrap();
        // Double-check: outra thread pode ter inserido enquanto esperávamos
        escrita
            .entry(chave.to_string())
            .or_insert_with(|| valor.clone());
    }
    valor
}

fn main() {
    let cache: Cache = Arc::new(RwLock::new(HashMap::new()));
    let chaves = ["user:1", "user:2", "user:1", "config", "user:2"];

    let handles: Vec<_> = chaves
        .iter()
        .map(|chave| {
            let cache = Arc::clone(&cache);
            let chave = chave.to_string();
            thread::spawn(move || {
                let resultado = buscar(&cache, &chave);
                println!("{} -> {}", chave, resultado);
            })
        })
        .collect();

    for h in handles {
        h.join().unwrap();
    }

    println!("Cache: {:?}", *cache.read().unwrap());
}

Padrões Comuns e Anti-padrões

Padrão: minimizar o tempo de lock

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let dados = Arc::new(Mutex::new(Vec::new()));

    let dados_clone = Arc::clone(&dados);
    let handle = thread::spawn(move || {
        // BOM: segurar o lock pelo menor tempo possível
        let item = calcular_algo_demorado();
        {
            let mut guard = dados_clone.lock().unwrap();
            guard.push(item);
        } // lock liberado imediatamente

        // RUIM: segurar o lock durante cálculo demorado
        // let mut guard = dados_clone.lock().unwrap();
        // let item = calcular_algo_demorado(); // outras threads bloqueadas!
        // guard.push(item);
    });

    handle.join().unwrap();
    println!("Dados: {:?}", *dados.lock().unwrap());
}

fn calcular_algo_demorado() -> i32 {
    std::thread::sleep(std::time::Duration::from_millis(100));
    42
}

Anti-padrão: deadlock por locks aninhados

use std::sync::Mutex;

fn main() {
    let a = Mutex::new(1);
    let b = Mutex::new(2);

    // PERIGO DE DEADLOCK se outra thread fizer lock(b) depois lock(a)
    // Sempre adquira locks na mesma ordem!
    let guard_a = a.lock().unwrap();
    let guard_b = b.lock().unwrap();
    println!("a={}, b={}", *guard_a, *guard_b);
    drop(guard_b);
    drop(guard_a);

    // SEGURO: sempre adquirir na mesma ordem (a antes de b)
    // ou usar try_lock para evitar deadlock
}

Garantias de Thread Safety

  • Mutex<T> é Send e Sync se T: Send. Isso significa que o Mutex pode ser compartilhado entre threads desde que o valor interno seja transferível.
  • RwLock<T> é Send e Sync se T: Send + Sync.
  • O MutexGuard implementa Deref e DerefMut, permitindo acesso transparente ao valor interno.
  • O lock é sempre liberado quando o guard é dropado — mesmo em caso de panic (mas o Mutex fica poisoned).
  • Mutex do Rust não é reentrante: tentar lock() duas vezes na mesma thread causa deadlock.

Veja Também