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:
- Há 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
| Item | Descriçã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
| Item | Descriçã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>éSendeSyncseT: Send. Isso significa que o Mutex pode ser compartilhado entre threads desde que o valor interno seja transferível.RwLock<T>éSendeSyncseT: Send + Sync.- O
MutexGuardimplementaDerefeDerefMut, permitindo acesso transparente ao valor interno. - O lock é sempre liberado quando o guard é dropado — mesmo em caso de panic (mas o Mutex fica poisoned).
Mutexdo Rust não é reentrante: tentarlock()duas vezes na mesma thread causa deadlock.
Veja Também
- std::thread em Rust — criação e gerenciamento de threads
- Channels em Rust: mpsc — alternativa: comunicação por mensagens
- Tipos Atômicos — sincronização sem lock para tipos simples
- Barrier e Condvar — sincronização avançada com Mutex
- Padrões de Thread Safety — Arc<Mutex
> e outros padrões - Rc e Arc em Rust — ponteiros de contagem de referência
- Concorrência em Rust — tutorial completo
- Documentação oficial:
std::sync::Mutex