Nebenläufigkeit und Multithreading in Rust: Sicheres Programmieren

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

Nebenläufigkeit und Multithreading in Rust: Sicheren parallelen Code schreiben



Rust ist eine der wenigen Sprachen, die das Problem von Datenrennen (data races) bereits zur Kompilierzeit löst. Dank des Ownership- und Borrowing-Systems wird nebenläufiger Code in Rust nicht nur schnell, sondern auch sicher. In diesem Artikel behandeln wir die Grundlagen des Multithreading in Rust: Erstellung von Threads, Datenaustausch über Kanäle und Mutexe, sowie eine Einführung in das Modell "Fearless Concurrency".



1. Grundlagen des Multithreading: std::thread



Rust bietet integrierte Unterstützung für Threads über das Modul std::thread. Ein neuer Thread kann mit der Funktion thread::spawn erstellt werden, die einen Closure entgegennimmt.



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

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

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

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


Die Methode join() blockiert den Hauptthread, bis der Kindthread beendet ist. Ohne sie könnte das Programm beendet werden, bevor der Thread ausgeführt wurde.



2. Datenübertragung zwischen Threads: Kanäle (mpsc)



Für den Nachrichtenaustausch zwischen Threads wird in Rust das Modell des Multi-Producer, Single-Consumer-Kanals (mpsc) verwendet. Dies ist im Modul std::sync::mpsc implementiert.



use std::sync::mpsc;use std::thread;

fn main() { let (tx, rx) = mpsc::channel();

thread::spawn(move || { let val = String::from("Hallo aus dem Thread!"); tx.send(val).unwrap(); // val wurde bereits verschoben, kann nicht mehr verwendet werden });

let received = rx.recv().unwrap(); println!("Empfangen: {}", received);}


Beachten Sie das Schlüsselwort move im Closure – es übergibt das Eigentum an der Variable tx an den neuen Thread. Die Methode send() gibt ein Result zurück, das Err sein kann, wenn der Empfänger geschlossen wurde.



Um mehrere Produzenten zu erstellen, kann tx geklont werden:



let (tx, rx) = mpsc::channel();let tx1 = tx.clone();

thread::spawn(move || { tx.send("Nachricht 1").unwrap();});

thread::spawn(move || { tx1.send("Nachricht 2").unwrap();});

for received in rx { println!("Empfangen: {}", received);}


3. Gemeinsamer Speicher: Mutex und Arc



Kanäle sind praktisch für die Datenübertragung, aber manchmal muss ein Zustand zwischen Threads geteilt werden. Dafür wird Mutex (gegenseitiger Ausschluss) in Verbindung mit Arc (atomarer Referenzzähler) verwendet.



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


Warum Arc und nicht Rc? Ein normaler Rc ist nicht threadsicher, da sein Referenzzähler nicht atomar ist. Arc verwendet atomare Operationen, was ihn für die Nebenläufigkeit sicher macht.



Wichtig: Mutex blockiert den Thread beim Aufruf von lock(). Nach Verlassen des Gültigkeitsbereichs wird die Sperre automatisch aufgehoben (dank des Drop-Traits).



4. Das Fearless Concurrency-Modell und Send/Sy

Blogs

Buchempfehlungen