Ce face Mutex
Mutex<T> (mutual exclusion) ține o valoare la care are acces un singur fir odată. lock() blochează firul până când mutex-ul este liber și returnează un MutexGuard, prin care citești și modifici valoarea ca printr-un &mut T. Când guard-ul dispare, mutex-ul este eliberat automat.
lock() returnează un Result: dacă un fir a intrat în panică în timp ce ținea mutex-ul, acesta devine „otrăvit” (poisoned) și lock() dă Err. De obicei se scrie lock().unwrap().
Pentru a împărți mutex-ul între fire, îl pui într-un Arc (Arc<Mutex<T>>) sau îl folosești prin referință în thread::scope. Mutex::new este const, deci poate fi și static.
Parametri
| Parametru | Descriere |
|---|---|
t |
Valoarea protejată (la new). |
Ce returnează
lock: Ok(MutexGuard), sau Err dacă mutex-ul este otrăvit.
Exemple
Exemplul 1: Contor comun pentru patru fire
Fiecare fir adaugă 1000. Datorită mutex-ului, nicio adunare nu se pierde, iar rezultatul este mereu 4000.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let contor = Arc::new(Mutex::new(0));
let mut fire = Vec::new();
for _ in 0..4 {
let contor = Arc::clone(&contor);
fire.push(thread::spawn(move || {
for _ in 0..1000 {
*contor.lock().unwrap() += 1;
}
}));
}
for f in fire {
f.join().unwrap();
}
println!("total: {}", *contor.lock().unwrap());
}
Rezultat
total: 4000
Exemplul 2: Listă completată din mai multe fire
Ordinea în care firele adaugă diferă de la o rulare la alta, așa că sortăm lista înainte de afișare.
use std::sync::Mutex;
use std::thread;
fn main() {
let rezultate = Mutex::new(Vec::new());
let orase = ["Iași", "Cluj", "Timișoara", "Brașov"];
thread::scope(|s| {
for oras in orase {
let rezultate = &rezultate;
s.spawn(move || {
let linie = format!("{oras}: {} litere", oras.chars().count());
rezultate.lock().unwrap().push(linie);
});
}
});
let mut lista = rezultate.into_inner().unwrap();
lista.sort();
for l in lista {
println!("{l}");
}
}
Rezultat
Brașov: 6 litere
Cluj: 4 litere
Iași: 4 litere
Timișoara: 9 litere
Exemplul 3: Mutex static și eliberarea guard-ului
Guard-ul se eliberează la finalul blocului. Fără blocul interior, al doilea lock() din același fir ar aștepta la nesfârșit.
use std::sync::Mutex;
static JURNAL: Mutex<Vec<&str>> = Mutex::new(Vec::new());
fn scrie(mesaj: &'static str) {
JURNAL.lock().unwrap().push(mesaj);
}
fn main() {
{
let mut jurnal = JURNAL.lock().unwrap();
jurnal.push("pornire");
jurnal.push("conectare");
}
scrie("gata");
println!("{:?}", JURNAL.lock().unwrap());
}
Rezultat
["pornire", "conectare", "gata"]
Greșeli frecvente
- Ții guard-ul într-o variabilă și apelezi o funcție care face și ea
lock()pe același mutex: firul se blochează pentru totdeauna (deadlock). Mutex-ul din Rust nu este reentrant. - Ții mutex-ul blocat în timpul unei operații lente (citire de fișier,
sleep): celelalte fire așteaptă degeaba. Copiază ce îți trebuie și eliberează guard-ul repede. - Uiți de
Arc: unMutexsimplu nu poate fi mutat în mai multe fire create cuthread::spawn.
Comenzi înrudite
Mutex::lock: Obține accesul exclusiv la valoarea din Mutex, așteptând dacă alt fir o folosește; returnează un guard.RwLock: Lacăt cititori-scriitor: mai multe fire pot citi în același timp, dar scrierea are nevoie de acces exclusiv.Arc: Varianta lui Rc care poate fi împărțită între fire: contorul de proprietari este actualizat atomic.AtomicUsize: Număr întreg care poate fi citit și modificat din mai multe fire fără Mutex, cu operații atomice.thread::scope: Pornește fire care pot împrumuta variabile locale; scope așteaptă automat terminarea tuturor firelor create în el.