Mutex & RwLock
Quando threads precisam compartilhar estado mutável, Mutex e RwLock serializam o acesso para que apenas um escritor ou muitos leitores toquem nos dados por vez. Emparelhe-os com Arc para compartilhamento entre múltiplas threads.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
use std::sync::{Arc, Mutex};
fn main() {
let data = Arc::new(Mutex::new(Vec::new()));
let mut guard = data.lock().unwrap();
guard.push(1);
}Quando usar isso: Contadores compartilhados, caches ou estado na memória atualizado por múltiplas threads quando a passagem de mensagens é complicada.
Exemplo de Trabalho
use std::sync::{Arc, RwLock};
use std::thread;
#[derive(Default)]
struct Cache {
entries: RwLock<std::collections::HashMap<String, String>>,
}
fn main() {
let cache = Arc::new(Cache::default());
let mut handles = vec![];
for i in 0..8 {
let cache = Arc::clone(&cache);
handles.push(thread::spawn(move || {
let key = format!("key-{i}");
if i % 3 == 0 {
let mut map = cache.entries.write().unwrap();
map.insert(key, format!("value-{i}"));
} else {
let map = cache.entries.read().unwrap();
let _ = map.get(&key);
}
}));
}
for h in handles {
h.join().unwrap();
}
}O que isso demonstra:
RwLockpermite leitores concorrentes.writeexclui todo o outro acesso.Arccompartilha o bloqueio entre threads.
Análise Profunda
Como Funciona
- Mutex: Acesso exclusivo. Uma guarda por vez. Simples e previsível.
- RwLock: Muitos leitores OU um escritor. Melhor quando as leituras dominam.
- Envenenamento (Poisoning): Se uma thread entrar em pânico enquanto segura um bloqueio, o mutex é "envenenado".
lock()retornaErr- recupere cominto_inner()ou propague a falha. - Estacionamento (Parking): Bloqueios disputados colocam as threads para dormir em vez de fazer busy-spinning (dependente do SO).
std::sync vs parking_lot
| Crate | Prós | Contras |
|---|---|---|
std::sync::Mutex | Sem dependência extra | Ligeiramente mais lento, modelo de envenenamento |
parking_lot::Mutex | Mais rápido, sem envenenamento | Dependência externa |
Notas de Rust
// Mantenha as seções críticas curtas
{
let mut guard = mutex.lock().unwrap();
guard.push(item);
} // guard é descartado aqui - bloqueio liberado antes de I/O lento
// Nunca segure um bloqueio através de .await em código asyncMutexGuarddereferencia para os dados internos viaDerefMut.- Prefira
parking_lotem caminhos críticos quando a profilaxia mostrar sobrecarga de bloqueio. - Considere
DashMappara hash maps concorrentes fragmentados.
Armadilhas
- Deadlocks de ordem de bloqueio - Thread A bloqueia
m1e depoism2; B faz o reverso. Correção: Sempre adquira bloqueios em uma ordem global fixa. - Seções críticas longas - Segurar bloqueios durante I/O bloqueia todos os esperadores. Correção: Clone os dados para fora, libere o bloqueio e, em seguida, faça I/O.
- RwLock para cargas de trabalho com muitas escritas - Escritores famintos impedem leitores e uns aos outros. Correção: Use
Mutexou canais. - Mutex para dados somente leitura - Contenção desnecessária. Correção: Use
Arc<T>quando os dados são imutáveis após a construção. - Ignorar erros de envenenamento -
unwrap()em envenenamento pode ocultar falhas em cascata. Correção: Registre e reinicie o estado ou desligue o serviço.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Canais | Produtores entregam trabalho de propriedade | Muitas threads precisam ler o mesmo snapshot repetidamente |
| Atômicos | Contadores e flags simples | Estruturas de dados complexas |
DashMap / bloqueios fragmentados | Mapas de alta concorrência | Você precisa de serialização estrita em um objeto |
Arc imutável + substituição | Atualizações raras | Mutação frequente no local |
FAQs
Mutex ou RwLock?
Use RwLock quando as leituras são muito mais frequentes que as escritas. Caso contrário, Mutex é mais simples e muitas vezes mais rápido sob carga de escrita.
O que é envenenamento de bloqueio?
Um pânico enquanto segura um bloqueio o marca como envenenado. Outras threads detectam possível estado inconsistente via lock() retornando Err.
Posso usar try_lock?
Sim. try_lock retorna imediatamente com Ok ou Err em vez de bloquear - útil para evitar deadlocks ou pular trabalho.
Por que parking_lot?
Bloqueios menores e mais rápidos sem a sobrecarga de envenenamento do std. Comum em bases de código Rust de produção.
Mutex em async Tokio?
Use tokio::sync::Mutex para bloqueio ciente de async, ou evite bloqueios com passagem de mensagens. Nunca segure std::sync::MutexGuard através de .await.
Como testar contenção de bloqueio?
Teste de carga com muitas threads e meça a latência p99. Se a contenção for alta, fragmentar dados ou mudar para passagem de mensagens.
Bloqueio recursivo?
std::sync::Mutex não é recursivo. parking_lot::ReentrantMutex existe se você realmente precisar de reentrada (raro).
Fome de leitor?
Algumas implementações de RwLock famintos deixam escritores ou leitores sob carga extrema. Verifique a documentação ou use Mutex se a justiça for importante.
Mutex em single-thread?
std::sync::Mutex ainda funciona em uma thread e fornece mutabilidade interior com compartilhamento 'static via Arc posteriormente.
Quando evitar bloqueios completamente?
Quando você pode enviar mensagens, usar atômicos ou particionar dados para que cada thread possua sua fatia (Rayon, sharding).
Relacionado
- Arc para Estado Compartilhado - Padrão
Arc<Mutex<T>> - Send & Sync - por que bloqueios são Sync
- Deadlocks & Condições de Corrida - ordem de bloqueio
- Estado Compartilhado em Async - padrões de mutex async
- Melhores Práticas de Concorrência - minimize o escopo do bloqueio
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, e Polars 0.46+.