parking_lot für Rust: Schnelle Synchronisierungs-Primitive
Die Bibliothek parking_lot ist eine der beliebtesten und leistungsfähigsten Implementierungen von Synchronisierungs-Primitiven für die Programmiersprache Rust. Sie bietet eine Alternative zu den standardmäßigen Mutexen (std::sync::Mutex), Read-Write-Locks (std::sync::RwLock) und Condition-Variablen (std::sync::Condvar), jedoch mit deutlich besserer Performance und geringerem Code-Overhead.
Warum wird diese Bibliothek benötigt? Die standardmäßigen Synchronisierungs-Primitive in Rust sind zwar sicher, haben aber einige Nachteile: Sie verwenden selbst in einfachen Szenarien Systemaufrufe (futex unter Linux), benötigen zusätzlichen Speicher für Flags und haben eine komplexere interne Implementierung. parking_lot löst diese Probleme durch effizientere Algorithmen (z. B. optimistische Sperren und Spin-Loops) und minimiert den Overhead. Das Ergebnis ist schnellerer Code, besonders in Multithread-Anwendungen mit hohem Konkurrenzdruck.
Die Bibliothek ist vollständig kompatibel mit Rust und erfordert keinen unsafe-Code auf der Benutzerseite. Sie bietet dieselben Schnittstellen wie die Standardbibliothek, jedoch mit zusätzlichen Funktionen: Mutex mit Timeout-Unterstützung, RwLock mit fairer Warteschlange, Once für faule Initialisierung und ReentrantMutex für reentrante Sperren. parking_lot ist der De-facto-Standard für leistungsstarke Rust-Anwendungen, von Webservern bis hin zu Spiele-Engines.
Installation
Fügen Sie die Bibliothek in Ihre Cargo.toml-Datei ein:
[dependencies]
parking_lot = "0.12"
Führen Sie dann den folgenden Befehl aus:
cargo build
Oder, wenn Sie cargo add verwenden (ab Rust 1.62):
cargo add parking_lot
Danach können Sie die Primitive aus der Bibliothek importieren:
use parking_lot::{Mutex, RwLock, Condvar, Once, ReentrantMutex};
Schnellstart: Minimales Arbeitsbeispiel
Hier ist ein einfaches Beispiel zur Verwendung von Mutex aus parking_lot, um einen gemeinsamen Zähler in einer Multithread-Umgebung zu schützen:
use parking_lot::Mutex;
use std::thread;
fn main() {
// Erstellen eines Mutex, der einen Wert umschließt
let counter = Mutex::new(0);
// Erstellen von 10 Threads, die den Zähler jeweils um 1 erhöhen
let mut handles = vec![];
for _ in 0..10 {
let handle = thread::spawn(|| {
// Die Sperre anfordern – .lock() gibt einen RAII-Guard zurück
let mut guard = counter.lock();
*guard += 1;
// Der Guard wird automatisch freigegeben, wenn er den Gültigkeitsbereich verlässt
});
handles.push(handle);
}
// Warten, bis alle Threads fertig sind
for handle in handles {
handle.join().unwrap();
}
// Den endgültigen Wert auslesen
let final_value = *counter.lock();
println!("Endgültiger Wert: {}", final_value); // Sollte 10 sein
}
In diesem Beispiel gibt counter.lock() einen MutexGuard zurück, der die Sperre beim Verlassen des Gültigkeitsbereichs automatisch freigibt. Im Gegensatz zum standardmäßigen std::sync::Mutex müssen hier keine Fehler (Poisoning) behandelt werden – parking_lot vergiftet den Mutex bei einer Panik standardmäßig nicht.
Wichtige Methoden und Typen
Im Folgenden werden 8 wichtige Methoden/Typen der parking_lot-Bibliothek mit Signaturen, Beschreibungen und Beispielen vorgestellt.
1. Mutex::lock()
Signatur: pub fn lock(&self) -> MutexGuard<'_, T>
Beschreibung: Sperrt den Mutex und blockiert den aktuellen Thread, bis die Sperre erlangt wird. Gibt einen RAII-Guard zurück, der die Sperre beim Verlassen des Gültigkeitsbereichs automatisch freigibt.
use parking_lot::Mutex;
let mutex = Mutex::new(42);
{
let mut guard = mutex.lock();
*guard += 1;
} // Sperre wird hier automatisch freigegeben
println!("Wert: {}", *mutex.lock());
2. Mutex::try_lock()
Signatur: pub fn try_lock(&self) -> Option<MutexGuard<'_, T>>
Beschreibung: Versucht, die Sperre zu erlangen, ohne den Thread zu blockieren. Gibt Some(guard) zurück, wenn die Sperre erfolgreich war, andernfalls None.
use parking_lot::Mutex;
let mutex = Mutex::new(10);
if let Some(mut guard) = mutex.try_lock() {
*guard += 5;
println!("Sperre erhalten, Wert geändert");
} else {
println!("Sperre nicht verfügbar");
}
3. RwLock::read()
Signatur: pub fn read(&self) -> RwLockReadGuard<'_, T>
Beschreibung: Erlangt eine Lesesperre. Mehrere Lesesperren können gleichzeitig gehalten werden, solange keine Schreibsperre aktiv ist. Blockiert den Thread, falls eine Schreibsperre aktiv ist.
use parking_lot::RwLock;
use std::thread;
let data = RwLock::new(vec![1, 2, 3]);
let reader1 = thread::spawn(|| {
let guard = data.read();
println!("Leser 1: {:?}", *guard);
});
let reader2 = thread::spawn(|| {
let guard = data.read();
println!("Leser 2: {:?}", *guard);
});
reader1.join().unwrap();
reader2.join().unwrap();
4. RwLock::write()
Signatur: pub fn write(&self) -> RwLockWriteGuard<'_, T>
Beschreibung: Erlangt eine exklusive Schreibsperre. Blockiert den Thread, bis alle Lesesperren und andere Schreibsperren freigegeben sind.
use parking_lot::RwLock;
let data = RwLock::new(5);
{
let mut guard = data.write();
*guard += 10;
} // Schreibsperre wird hier freigegeben
println!("Neuer Wert: {}", *data.read());
5. Condvar::wait()
Signatur: pub fn wait<T>(&self, mutex_guard: &mut MutexGuard<'_, T>)
Beschreibung: Blockiert den aktuellen Thread, bis die Condition-Variable benachrichtigt wird. Die Mutex-Sperre wird während des Wartens freigegeben und nach dem Aufwachen automatisch wieder erlangt.
use parking_lot::{Mutex, Condvar};
use std::thread;
let pair = (Mutex::new(false), Condvar::new());
let (lock, cvar) = &pair;
thread::spawn(move || {
let mut ready = lock.lock();
*ready = true;
cvar.notify_one();
});
let mut ready = lock.lock();
while !*ready {
cvar.wait(&mut ready);
}
println!("Bereit!");
6. Condvar::notify_one()
Signatur: pub fn notify_one(&self)
Beschreibung: Weckt genau einen wartenden Thread auf, der auf diese Condition-Variable wartet. Falls kein Thread wartet, hat der Aufruf keine Wirkung.
use parking_lot::{Mutex, Condvar};
let pair = (Mutex::new(0), Condvar::new());
let (lock, cvar) = &pair;
// Thread 1 wartet
thread::spawn(move || {
let mut count = lock.lock();
while *count < 1 {
cvar.wait(&mut count);
}
println!("Aufgewacht!");
});
// Thread 2 benachrichtigt
thread::spawn(move || {
let mut count = lock.lock();
*count += 1;
cvar.notify_one();
});
7. Once::call_once()
Signatur: pub fn call_once<F>(&self, f: F) where F: FnOnce()
Beschreibung: Führt die übergebene Closure genau einmal aus, unabhängig davon, wie oft call_once aufgerufen wird. Ideal für die faule Initialisierung von globalen Ressourcen.
use parking_lot::Once;
use std::sync::atomic::{AtomicUsize, Ordering};
static INIT: Once = Once::new();
static mut GLOBAL_VALUE: AtomicUsize = AtomicUsize::new(0);
fn get_global() -> usize {
INIT.call_once(|| {
unsafe { GLOBAL_VALUE.store(42, Ordering::SeqCst); }
});
unsafe { GLOBAL_VALUE.load(Ordering::SeqCst) }
}
fn main() {
println!("Globaler Wert: {}", get_global());
println!("Erneuter Aufruf: {}", get_global()); // Führt die Closure nicht erneut aus
}
8. ReentrantMutex::lock()
Signatur: pub fn lock(&self) -> ReentrantMutexGuard<'_, T>
Beschreibung: Erlangt eine reentrante Sperre. Derselbe Thread kann die Sperre mehrfach erlangen, ohne sich selbst zu blockieren. Nützlich für rekursive Aufrufe oder Callbacks.
use parking_lot::ReentrantMutex;
let mutex = ReentrantMutex::new(0);
fn rekursive_funktion(mutex: &ReentrantMutex, tiefe: u32) {
let mut guard = mutex.lock();
*guard += 1;
if tiefe > 0 {
rekursive_funktion(mutex, tiefe - 1);
}
}
fn main() {
rekursive_funktion(&mutex, 5);
println!("Endgültiger Wert: {}", *mutex.lock());
}
Fazit
parking_lot ist eine unverzichtbare Bibliothek für alle Rust-Entwickler, die Wert auf Leistung und Effizienz legen. Sie bietet eine vollständige und optimierte Alternative zu den Standard-Synchronisierungs-Primitiven und ist dabei einfach zu bedienen. Ob für Webserver, Datenbanken oder Spiele – mit parking_lot holen Sie das Maximum aus Ihrer Multithread-Anwendung heraus.
Probieren Sie es noch heute in Ihrem nächsten Rust-Projekt aus und überzeugen Sie sich selbst von der Leistungssteigerung!
Auch in der Bibliothek
MailKit für C# (.NET): Komplettguide mit Praxisbeispielen
JWT-Tokens in JavaScript (Node.js): Vollständiger Leitfaden
Arbeit mit Kafka in Java: Bibliotheken, Konfiguration und Codebeispiele