AtomicUsize în Rust

Număr întreg care poate fi citit și modificat din mai multe fire fără Mutex, cu operații atomice.

Sintaxă
use std::sync::atomic::{AtomicUsize, Ordering};

const fn new(v: usize) -> AtomicUsize
fn fetch_add(&self, val: usize, order: Ordering) -> usize
fn load(&self, order: Ordering) -> usize
fn store(&self, val: usize, order: Ordering)

Ce face AtomicUsize

AtomicUsize ține un usize pe care toate firele îl pot modifica prin &, fără lacăt. Operațiile atomice nu pot fi întrerupte la jumătate: fetch_add(n, ...) adună n și returnează valoarea de dinainte, load(...) citește valoarea, store(v, ...) o înlocuiește.

Argumentul Ordering spune ce garanții de ordine are operația față de restul memoriei. Dacă nu ești sigur, folosește Ordering::SeqCst, cea mai strictă. Pentru un simplu contor de statistică ajunge Ordering::Relaxed.

Există tipuri atomice și pentru alte valori: AtomicBool, AtomicI32, AtomicU64 etc., cu aceleași metode. Pentru că new este const, pot fi static.

Parametri

Parametru Descriere
val Valoarea adunată (fetch_add) sau scrisă (store).
order Ordinea memoriei: SeqCst, Relaxed, Acquire, Release, AcqRel.

Ce returnează

fetch_add: valoarea de dinainte. load: valoarea curentă. store: nimic.

Exemple

Exemplul 1: Contor de cereri din mai multe fire

Patru fire adună câte 250; rezultatul final este mereu 1000. Nu e nevoie de Mutex.

use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;

static CERERI: AtomicUsize = AtomicUsize::new(0);

fn main() {
    thread::scope(|s| {
        for _ in 0..4 {
            s.spawn(|| {
                for _ in 0..250 {
                    CERERI.fetch_add(1, Ordering::Relaxed);
                }
            });
        }
    });
    println!("cereri: {}", CERERI.load(Ordering::SeqCst));
}

Rezultat

cereri: 1000

Exemplul 2: fetch_add returnează valoarea veche; store o înlocuiește

Fiecare comandă primește un număr unic: valoarea returnată de fetch_add.

use std::sync::atomic::{AtomicUsize, Ordering};

fn main() {
    let urmatorul_id = AtomicUsize::new(100);

    for client in ["Ana", "Mihai", "Ioana"] {
        let id = urmatorul_id.fetch_add(1, Ordering::SeqCst);
        println!("comanda #{id} pentru {client}");
    }
    println!("următorul id: {}", urmatorul_id.load(Ordering::SeqCst));

    urmatorul_id.store(1, Ordering::SeqCst);
    println!("după resetare: {}", urmatorul_id.load(Ordering::SeqCst));
}

Rezultat

comanda #100 pentru Ana
comanda #101 pentru Mihai
comanda #102 pentru Ioana
următorul id: 103
după resetare: 1

Greșeli frecvente

  • Faci let v = a.load(..); a.store(v + 1, ..); din mai multe fire: între cele două operații alt fir poate scrie și se pierd adunări. Folosește fetch_add.
  • Te aștepți ca fetch_add să returneze valoarea nouă: returnează valoarea de dinainte de adunare.
  • Depășirea nu este verificată: fetch_add trece prin 0 la depășire (wrapping), fără panică.

Comenzi înrudite

  • Mutex: Protejează o valoare folosită din mai multe fire: lock() așteaptă până când firul curent este singurul cu acces.
  • thread::scope: Pornește fire care pot împrumuta variabile locale; scope așteaptă automat terminarea tuturor firelor create în el.
  • Arc: Varianta lui Rc care poate fi împărțită între fire: contorul de proprietari este actualizat atomic.
  • Cell: Celulă cu mutabilitate interioară pentru valori Copy: get() copiază valoarea afară, set() pune una nouă, prin &self.