Parallelität in Rust: Threads, Goroutinen, async/await – eine vollständige Analyse

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 in Rust: Von Mutexen bis async/await



Wenn es um Hochleistungssysteme geht, steht Rust sofort im Mittelpunkt. Aber was macht es wirklich einzigartig? Es ist die Parallelität in Rust – ein Ansatz, bei dem Sie die Geschwindigkeit von C und Speichersicherheit erhalten, ohne die Lesbarkeit des Codes zu opfern. In diesem Artikel analysieren wir drei Hauptwerkzeuge: klassische Threads, leichte Goroutinen (im Kontext von Rust – asynchrone Aufgaben) und modernes async/await. Los geht's!



Warum hat Rust keine Angst vor Parallelität?

Im Gegensatz zu vielen Sprachen löst Rust das Problem von Datenrennen auf Compiler-Ebene. Das Ownership- und Borrowing-System stellt sicher, dass Sie nicht versehentlich Daten aus zwei Threads gleichzeitig ändern können. Das ist keine Magie – es ist eine strenge statische Prüfung. Deshalb gilt Multithreading in Rust als eines der sichersten.



1. Threads (std::thread)

Klassische OS-Threads sind die Grundlage. In Rust werden sie über das Modul std::thread implementiert. Jeder Thread ist ein separater, leichtgewichtiger Prozess, der parallel auf verschiedenen CPU-Kernen ausgeführt werden kann.



Beispiel: Threads starten

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


Hier erstellt thread::spawn einen neuen Thread, und join wartet auf dessen Beendigung. Einfach, aber es gibt einen Haken: Threads sind „schwer". Jeder Thread verbraucht mindestens 2-8 MB Stack-Speicher. Für Tausende gleichzeitiger Aufgaben ist das ungeeignet.



Wann sollte man Threads verwenden?

  • Wenn echter Parallelismus benötigt wird (CPU-gebundene Aufgaben).
  • Wenn die Anzahl der Threads die Anzahl der Kerne * 2 nicht überschreitet.
  • Wenn Sie mit blockierenden Operationen arbeiten (Dateilesen, synchrone Aufrufe).


2. Goroutinen in Rust: Mythos oder Realität?

Streng genommen gibt es in Rust keine Goroutinen im Sinne von Go. Aber es gibt asynchrone Aufgaben (tasks), die sich fast genauso verhalten: Sie sind leichtgewichtig, wechseln kooperativ (ohne OS-Beteiligung) und können zu Tausenden auf einem Thread ausgeführt werden. Dazu wird die Laufzeitumgebung Tokio oder async-std verwendet.



Beispiel: Asynchrone Aufgabe (Goroutine) mit Tokio

use tokio::task;

#[tokio::main]async fn main() { let handle = task::spawn(async { for i in 1..10 { println!("Goroutine: {}", i); tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; } });

for i in 1..5 { println!("Haupt: {}", i); tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; }

handle.await.unwrap();}


Beachten Sie: task::spawn erstellt eine leichte Aufgabe, die innerhalb des Tokio-Thread-Pools ausgeführt wird. Der Speicherverbrauch beträgt etwa 2-3 KB pro Aufgabe. Dies ermöglicht das Ausführen von 100.000 „Goroutinen" ohne Probleme. Im Gegensatz zu Go gibt es hier keinen eingebauten Scheduler – Sie wählen die Laufzeitumgebung selbst.



Wie unterscheiden sich Rust-Goroutinen von Go?

  • In Rust – Aufgaben sind kooperativ: Sie geben die Kontrolle selbst über .await ab.
  • In Go – Goroutinen sind präemptiv: Der Scheduler unterbricht sie an Ein-/Ausgabepunkten.
  • Rust bietet mehr Kontrolle: Sie können eine ein- oder mehrthreadige Laufzeitumgebung wählen.


3. Async/await: Das Herz von asynchronem Rust

Die Schlüsselwörter async und .await verwandeln synchronen Code in asynchronen, ohne s

Blogs

Buchempfehlungen