ProgrammatieBackend ontwikkelaar

Welke manieren van thread-synchronisatie biedt de standaardbibliotheek van Rust? Hoe kies je tussen hen en welke 'fijne plekken' moet je zeker in overweging nemen?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord

De standaardbibliotheek van Rust biedt de belangrijkste synchronisatieprimitieven voor veilige multithreading:

  • Mutex — zorgt voor mutuele uitsluiting voor toegang tot gegevens vanuit meerdere threads;
  • RwLock — staat gelijktijdig meerdere lezers (read) toe, maar slechts één schrijver (write);
  • Condvar — primitieve voor voorwaardelijke variabelen om threads te wekken op basis van een gebeurtenis;
  • Atomic types (AtomicBool, AtomicUsize, enz.) — lees/schrijf operaties zonder locking, op hardware-niveau;
  • Arc (Atomic Reference Counted) — referentietelling met threadveiligheid, voor gedeeld eigendom van objecten.

Keuze:

  • Als alleen lezen vereist is — gebruik RwLock (efficiënter dan Mutex).
  • Voor eenvoudige gesynchroniseerde toegang van één thread — Mutex.
  • Voor synchronisatie op basis van een signaal (bijvoorbeeld 'wachten op een nieuw element') — Condvar.
  • Voor atomische tellers/vlaggen — Atomic.
  • Voor gedeeld eigendom (multithreaded) — Arc.

Voorbeeld:

use std::sync::{Arc, Mutex}; use std::thread; fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap()); }

Vraag met een valstrik

Vraag: Garandeert Rust dat het gebruik van Mutex<T> je volledig vrijwaart van deadlock door controle tijdens de compilatie?

Antwoord: Nee. Rust garandeert veilige toegang tot gegevens via eigendom en borrow-checker, maar beschermt niet tegen deadlocks op taalniveau. Deadlocks ontstaan puur logisch bij schending van de volgorde van het verwerven van meerdere Mutexen of hun recursieve acquisitie. Voorbeeld:

use std::sync::Mutex; let lock1 = Mutex::new(0); let lock2 = Mutex::new(0); // Thread 1: lock1 -> lock2, Thread 2: lock2 -> lock1 ⇒ deadlock

Geschiedenis

Een datastreamproject ervaarde willekeurige "vastlopers". Het bleek dat ontwikkelaars geneste Mutex'en (Mutex binnen Mutex) gebruikten zonder strikte volgorde van verwerving, wat leidde tot deadlocks die alleen opgelost konden worden door het proces geforceerd af te sluiten.

Geschiedenis

In een grote service migreerden ze massaal van Mutex<Option<T>> naar RwLock<T>, zonder in overweging te nemen dat een writable lock langer kan duren dan een lock voor lezen. Bij piekbelasting resulteerde dit in verwerkingstijden van tientallen seconden door wachttijden op schrijven.

Geschiedenis

Programmeurs probeerden op threads te besparen, en "duwden" Arc<Mutex<_>> in honderden threads. Door de subtiliteiten van de scheduler en hergebruik van mutexen ontstonden ongelooflijke wederzijdse wachttijden — de prestaties daalden met een factor vijf!