Parallelität und Multithreading in Rust: Von A bis Z

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Parallelität und Multithreading in Rust: Von A bis Z



Moderne Anwendungen erfordern zunehmend die effiziente Nutzung von Mehrkernprozessoren. Rust bietet einen einzigartigen Ansatz für Parallelität und Multithreading, der hohe Leistung mit Speichersicherheitsgarantien zur Kompilierzeit kombiniert. In diesem Artikel behandeln wir die wichtigsten Konzepte: von der Thread-Erstellung bis zur Verwendung von Mutexen und Kanälen, mit praktischen Codebeispielen.



1. Grundlagen des Multithreadings: Threads erstellen und verwalten



In Rust werden Threads mit der Standardbibliothek std::thread erstellt. Jeder Thread wird unabhängig ausgeführt, kann aber Daten mit anderen Threads austauschen.



Ein einfaches Beispiel zur Thread-Erstellung:



use std::thread;use std::time::Duration;

fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("Thread: Zahl {}", i); thread::sleep(Duration::from_millis(1)); } });

for i in 1..5 { println!("Hauptthread: Zahl {}", i); thread::sleep(Duration::from_millis(1)); }

handle.join().unwrap(); // Warten auf das Ende des Threads}


Wichtige Punkte:

  • thread::spawn nimmt einen Closure entgegen, der in einem neuen Thread ausgeführt wird.
  • Die Methode join blockiert den aktuellen Thread, bis der angegebene Thread beendet ist.
  • Ohne join könnte der Hauptthread vor dem Kindthread enden.


Für die Datenübergabe zwischen Threads verwenden Sie Closures mit move:



use std::thread;

fn main() { let data = vec![1, 2, 3, 4, 5];

let handle = thread::spawn(move || { println!("Daten aus dem Thread: {:?}", data); });

handle.join().unwrap(); // println!("{:?}", data); // Fehler! data wurde bereits verschoben}


Das Schlüsselwort move überträgt den Besitz der Daten an den Closure und verhindert so Datenrennen zur Kompilierzeit.



2. Sicherer Datenaustausch: Mutexe und Sperren



Wenn mehrere Threads auf gemeinsame Daten zugreifen, besteht die Gefahr von Datenrennen. Rust löst dieses Problem mit Mutexen (Mutex) und atomaren Typen.



Beispiel für die Verwendung von Mutex mit Arc (atomarer Referenzzähler):



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()); // 10}


Wichtige Details:

  • Arc<T> ermöglicht mehreren Threads den Besitz derselben Daten (atomares Klonen).
  • Mutex::lock() gibt eine Sperre zurück; wenn die Sperre bereits belegt ist, wartet der Thread.
  • Die Sperre wird automatisch freigegeben, wenn der Gültigkeitsbereich verlassen wird.


Für eine feinere Kontrolle verwenden Sie RwLock, das Leser und Schreiber trennt:



use std::sync::RwLock;

let lock = RwLock::new(5);

// Lesen: mehrere Threads gleichzeitig{ let r1 = lock.read().unwrap(); let r2 = lock.read().unwrap(); println!("Lesen: {} und {}", r1, r2);}

// Schreiben: nur ein Thread{ let mut w = lock.write().unwrap(); *w += 1; println!("Geschrieben: {}", w);}


3. Kanäle zur Nachrichtenübertragung



Eine Alternative zum gemeinsamen Speicher ist das Akteursmodell mit Kanälen. Rust bietet einen Mehrfachproduzenten-, Einfachkonsumenten-Kanal (mpsc).



Basisbeispiel:



u                

Blogs

Buchempfehlungen