Historie:
Die Arbeit mit Multithreading ist eine Fehlerquelle in den meisten Programmiersprachen: Datenrennen, Ressourcenwettbewerb, nicht offensichtliche Bugs. Aus der Erfahrung mit C++ und Java haben die Schöpfer von Rust beschlossen, Mechanismen für die Thread-Sicherheit direkt in das Typsystem zu integrieren, um die meisten Fehler bereits zur Kompilierungszeit zu erkennen.
Problem:
In klassischen Sprachen ist man oft auf die Disziplin des Programmierers und externe Werkzeuge angewiesen: Risiken der Übergabe von Datenbesitz, gemeinsam genutzter veränderbarer Speicher und das Fehlen von Kontrolle über gleichzeitigen Zugriff können zu kritischen Fehlern führen. Es war notwendig, ein System zu gewährleisten, das keine Datenrennen zur Kompilierungszeit zulässt.
Lösung:
In Rust werden spezielle Typen aus der Standardbibliothek zur Synchronisation und zum Austausch von Daten zwischen Threads verwendet – beispielsweise Arc, Mutex und Kanäle. Eine Schlüsselrolle spielen die Trait-Marker Send und Sync, die automatisch vom Compiler überprüft werden. Ein Typ gilt als thread-safe, wenn:
Sync)Send implementiertBeispielcode:
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!("Ergebnis: {}", *counter.lock().unwrap()); }
Hauptmerkmale:
Mutex, RwLock, Kanäle – sind gemäß Vertrag thread-safeArc, Mutex) und nicht über Zeiger implementiertWarum kann man Rc<T> nicht zur Übergabe von Daten zwischen Threads verwenden?
Rc<T> implementiert das Trait Send nicht und ist nicht thread-safe – die interne Implementierung basiert auf einem nicht blockierenden Referenzzähler, was zu einem Datenrennen bei Zugriff aus mehreren Threads führt. Für Threads verwenden Sie Arc<T>.
Kann man Send oder Sync für einen eigenen Typ manuell implementieren, um die Einschränkungen des Compilers zu umgehen?
Ja, aber das ist extrem gefährlich! Wenn Invarianten verletzt werden (zum Beispiel ein nackter Zeiger geteilt wird), führt das zu einem Datenrennen. Lassen Sie die manuelle Implementierung nur für Fachleute, die sich der Thread-Sicherheit ihres Typs absolut sicher sind.
Wann kann ein Mutex in Rust zu einem Deadlock führen, und wie kann man das vermeiden?
Ein Deadlock kann auftreten, wenn die Reihenfolge der Sperrung mehrerer Mutex nicht stabil ist oder die Sperrung rekursiv in einem Thread vorgenommen wird (Mutex ist nicht reentrant!).
use std::sync::Mutex; let a = Mutex::new(0); let _g1 = a.lock().unwrap(); let _g2 = a.lock().unwrap(); // panic: deadlock!
unwrap bei der Arbeit mit lock() zu überprüfenEin Entwickler verwendete Rc<RefCell<T>> zur Übergabe des Zustands zwischen Threads in einem Webserver. In lokalen Tests funktionierte es, aber in der Produktion traten Datenrennen auf: Manchmal "verloren" Variablen ihren Zustand, manchmal stürzte der Server ab.
Vorteile:
Nachteile:
Verwendung von Arc<Mutex<T>> zur Übergabe des Zustands, strikte Einhaltung von Send/Sync, Verteilung der Arbeit auf Threads über Kanäle, keine mutierenden gemeinsamen Daten zwischen Threads.
Vorteile:
Nachteile: