Mutex în Rust

Protejează o valoare folosită din mai multe fire: lock() așteaptă până când firul curent este singurul cu acces.

Sintaxă
use std::sync::Mutex;

const fn new(t: T) -> Mutex<T>
fn lock(&self) -> LockResult<MutexGuard<'_, T>>

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: un Mutex simplu nu poate fi mutat în mai multe fire create cu thread::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.