Parallelität und Multithreading in Rust: Von den Grundlagen zu fortgeschrittenen Techniken
Rust hat sich den Ruf als eine der sichersten Sprachen für die Systemprogrammierung erworben, nicht zuletzt dank seines Ownership-Modells und des Typsystems, das Datenrennen bereits zur Kompilierzeit verhindert. Parallelität bedeutet jedoch nicht nur Sicherheit, sondern auch Effizienz. In diesem Artikel betrachten wir fortgeschrittene Aspekte der Arbeit mit Parallelität und Multithreading in Rust: von grundlegenden Synchronisationsprimitiven bis hin zu modernen asynchronen Laufzeiten. Das Material richtet sich an Entwickler, die bereits mit den Grundlagen der Sprache vertraut sind.
1. Sicheres Multithreading: Send, Sync und Arc<Mutex<T>>
Die Grundlage für sicheres Multithreading in Rust bilden zwei Schlüssel-Traits: Send und Sync. Ein Typ, der Send implementiert, kann sicher zwischen Threads übertragen werden. Ein Typ, der Sync implementiert, kann sicher per Referenz zwischen Threads geteilt werden. Die meisten Standardtypen (z. B. i32, String) implementieren beide Traits automatisch, wenn sie nur Send/Sync-Daten enthalten.
Für den gemeinsamen Zugriff auf veränderbare Daten aus mehreren Threads wird häufig das Muster Arc<Mutex<T>> verwendet. Arc (atomarer Referenzzähler) ermöglicht die gemeinsame Nutzung des Besitzes zwischen Threads, während Mutex den gegenseitigen Ausschluss gewährleistet.
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());}Wichtig zu beachten: Mutex in Rust ist nicht nur eine Sperre, sondern ein Container, der die Daten schützt. Das Erlangen der Sperre gibt einen intelligenten Zeiger MutexGuard zurück, der die Sperre automatisch freigibt, wenn er den Gültigkeitsbereich verlässt. Dies vermeidet den klassischen Fehler "Sperre freizugeben vergessen".
Fortgeschrittener Tipp: RwLock statt Mutex
Wenn Ihre Daten häufig gelesen, aber selten geändert werden, verwenden Sie RwLock. Es ermöglicht mehreren Threads das gleichzeitige Lesen der Daten, blockiert jedoch das Schreiben, solange es mindestens einen Leser gibt. Dies kann die Leistung in Szenarien mit "mehr Lesern als Schreibern" erheblich verbessern.
use std::sync::RwLock;
let data = RwLock::new(vec![1, 2, 3]);
// Lesen — mehrere Threads können gleichzeitig lesenlet read_guard = data.read().unwrap();println!("First element: {}", read_guard[0]);drop(read_guard); // Sperre explizit freigeben
// Schreiben — exklusiver Zugrifflet mut write_guard = data.write().unwrap();write_guard.push(4);2. Kanäle (Channels): Nachrichtenaustausch zwischen Threads
Kanäle sind eine Alternative zum gemeinsamen Speicher. In Rust gibt es zwei Haupttypen von Kanälen: std::sync::mpsc (Multi-Producer, Single-Consumer) und crossbeam-channel (Multi-Producer, Multi-Consumer).
Der Standard-mpsc eignet sich hervorragend für Szenarien mit "einem Konsumenten und vielen Produzenten". Er funktioniert nach dem Prinzip einer Warteschlange: Sender legen Nachrichten ab, der Empfänger holt sie in der FIFO-Reihenfolge ab.
use std::sync::mpsc;use std::thread;
fn main() { let (tx, rx) = mpsc::channel();
let tx1 = tx.clone(); thread::spawn(move || { tx1.send("Nachricht von Thread 1").unwrap(); });
thread::spawn(move || { tx.send("Nachricht von Thread 2").unwrap(); });
for received in rx { println!("Empfangen: {}", received); }}