Achtergrond:
Werken met multithreading is een bron van fouten in de meeste programmeertalen: race conditions, resource competitie, onduidelijke bugs. Door de ervaringen met C++ en Java hebben de makers van Rust besloten om mechanismen voor threadveiligheid recht in het typesysteem in te bouwen, zodat de meeste fouten al tijdens de compilatie worden ontdekt.
Probleem:
In klassieke talen moet men vaak vertrouwen op de discipline van de programmeur en externe hulpmiddelen: risico's van eigendomsoverdracht van gegevens, gedeeld veranderlijk geheugen en gebrek aan controle over gelijktijdige toegang kunnen leiden tot kritische fouten. Er moest een systeem worden opgericht dat garandeert dat er geen geheugenraces zijn tijdens de compilatiefase.
Oplossing:
In Rust worden speciale types uit de standaardbibliotheek gebruikt voor synchronisatie en gegevensoverdracht tussen threads — bijvoorbeeld, Arc, Mutex en kanalen. De sleutelrol wordt gespeeld door de trait markers Send en Sync, die automatisch door de compiler worden gecontroleerd. Een type wordt als thread-safe beschouwd als:
Sync)Send implementeertVoorbeeldcode:
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()); }
Belangrijke punten:
Mutex, RwLock, kanalen — zijn thread-safe volgens de overeenkomstArc, Mutex), en niet via pointersWaarom kan Rc<T> niet worden gebruikt voor gegevensoverdracht tussen threads?
Rc<T> implementeert de trait Send niet en is niet thread-safe — de interne implementatie is gebaseerd op een niet-blokkerende referentieteller, wat leidt tot data races bij toegang vanuit meerdere threads. Gebruik voor threads Arc<T>.
Kan ik Send of Sync handmatig implementeren voor mijn eigen type om de beperkingen van de compiler te omzeilen?
Ja, maar dit is uiterst gevaarlijk! Als je invarianten schendt (bijvoorbeeld door een ruwe pointer te delen), krijg je data races. Laat handmatige implementatie alleen over aan specialisten die volledig zeker zijn van de thread safety van het type.
Wanneer kan Mutex leiden tot deadlock in Rust, en hoe kan ik dit vermijden?
Deadlock is mogelijk als de volgorde van het verkrijgen van meerdere mutexes niet stabiel is of als de blokkering recursief op één thread wordt uitgevoerd (Mutex is niet reentrant!).
use std::sync::Mutex; let a = Mutex::new(0); let _g1 = a.lock().unwrap(); let _g2 = a.lock().unwrap(); // panic: deadlock!
Een ontwikkelaar gebruikte Rc<RefCell<T>> voor gegevensoverdracht tussen threads in een webserver. Het werkte op lokale tests, maar in productie verschenen race conditions: soms "verloor" de server de staat van variabelen, soms crashte de server.
Voordelen:
Nadelen:
Gebruik van Arc<Mutex<T>> voor gegevensoverdracht, strikte naleving van Send/Sync, verdeling van werk over threads via kanalen, geen mut toegang tot gedeelde gegevens in threads.
Voordelen:
Nadelen: