English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Eine sichere und effiziente Verarbeitung der Koncurrency ist einer der Ziele der Entstehung von Rust, hauptsächlich um die Belastbarkeit von Servern zu lösen.
Der Begriff der Koncurrency (concurrent) besagt, dass verschiedene Teile eines Programms unabhängig voneinander ausgeführt werden, was leicht mit dem Begriff der Parallelität (parallel) verwechselt werden kann. Parallelität betont die "Gleichzeitige Ausführung".
Koncurrency führt oft zu Parallelität.
Dieses Kapitel behandelt Programmierungskonzepte und Details, die mit der Koncurrency zu tun haben.
Ein Thread (thread) ist ein unabhängig laufender Teil eines Programms.
Der Unterschied zwischen Threads und Prozessen (process) liegt darin, dass Threads ein Konzept innerhalb eines Programms sind, und Programme werden oft innerhalb eines Prozesses ausgeführt.
In Umgebungen mit Betriebssystemen werden Prozesse oft abwechselnd geplant und ausgeführt, während Threads innerhalb eines Prozesses von einem Programm geplant werden.
Da die parallele Ausführung von Threads möglicherweise parallele Situationen verursacht, treten Deadlocks und Verzögerungsfehler, die in Programmen mit parallelen Mechanismen auftreten können, oft in parallelen Operationen auf.
Um diese Probleme zu lösen, nutzen viele andere Sprachen (wie Java, C#) spezielle Laufzeitsoftware (runtime), um Ressourcen zu koordinieren, was jedoch zweifellos die Ausführungsleistung der Programme erheblich verringert.
C/C++ Sprachen unterstützen in der tiefsten Ebene des Betriebssystems auch Multithreading, und die Sprache selbst sowie ihr Compiler verfügen nicht über die Fähigkeit, parallele Fehler zu erkennen und zu vermeiden, was für Entwickler einen großen Druck bedeutet. Entwickler müssen viel Energie darauf verwenden, Fehler zu vermeiden.
Rust hängt nicht vom Laufzeitumgebung ab, was wie C ist/C++ genauso.
Doch Rust hat in der Sprache selbst Mittel wie das Eigenschaftsmechanismus integriert, um die häufigsten Fehler so weit wie möglich in der Kompilierungsphase zu beseitigen, was andere Sprachen nicht bieten.
Das bedeutet jedoch nicht, dass wir bei der Programmierung vorsichtig sein können. Bislang sind die durch Konkurrierende Ausführung verursachten Probleme noch nicht vollständig in der Öffentlichkeit gelöst worden und es besteht immer noch die Möglichkeit, Fehler zu haben. Beim Konkurrenzprogramming sollte man vorsichtig sein!
In Rust wird ein neuer Prozess durch die Funktion std::thread::spawn erstellt:
use std::thread; use std::time::Duration; fn spawn_function() { for i in 0..5 { println!("erzeugter Thread druckt aus {}", i); thread::sleep(Duration::from_millis(1)); } } fn main() { thread::spawn(spawn_function); for i in 0..3 { println!("Hauptthread druckt aus {}", i); thread::sleep(Duration::from_millis(1)); } }
Laufender Output:
Hauptthread druckt aus 0 erzeugter Thread druckt aus 0 Hauptthread druckt aus 1 erzeugter Thread druckt aus 1 Hauptthread druckt aus 2 erzeugter Thread druckt aus 2
Diese Ergebnisse können in einigen Fällen in einer anderen Reihenfolge angezeigt werden, aber im Allgemeinen wird so gedruckt.
Dieser Programm hat einen Unterthread, der darauf abzielt, 5 Textzeilen, der Hauptthread druckt drei Textzeilen aus, aber es ist offensichtlich, dass mit dem Ende des Hauptthreads auch der erzeugte Thread endet und nicht alle Druckvorgänge abgeschlossen werden.
Der Parameter der Funktion std::thread::spawn ist eine funktionslose Funktion, aber die obige Schreibweise wird nicht empfohlen. Wir können Closures (Klammern) verwenden, um Funktionen als Parameter zu übergeben:
use std::thread; use std::time::Duration; fn main() { thread::spawn(|| { for i in 0..5 { println!("erzeugter Thread druckt aus {}", i); thread::sleep(Duration::from_millis(1)); } }); for i in 0..3 { println!("Hauptthread druckt aus {}", i); thread::sleep(Duration::from_millis(1)); } }
Klammern können in Variablen gespeichert oder als Parameter an andere Funktionen übergeben werden. Klammern sind ähnlich wie Lambda-Ausdrücke in Rust und haben die folgende Form:
|Parameter1, Parameter2, ...| -) > Rückgabetyp { // Funktionkörper }
Beispiel:
fn main() { let inc = |num: i32| -) > i32 { num + 1 }); println!("inc(5) = {}", inc(5)); }
Laufender Output:
inc(5) = 6
Klammern können weggelassen werden, um die Typdeklaration zu vermeiden und das automatische Typenbeurteilungssystem von Rust zu verwenden:
fn main() { let inc = |num| { num + 1 }); println!("inc(5) = {}", inc(5)); }
Das Ergebnis hat sich nicht geändert.
use std::thread; use std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 0..5 { println!("erzeugter Thread druckt aus {}", i); thread::sleep(Duration::from_millis(1)); } }); for i in 0..3 { println!("Hauptthread druckt aus {}", i); thread::sleep(Duration::from_millis(1)); } handle.join().unwrap(); }
Laufender Output:
Hauptthread druckt aus 0 erzeugter Thread druckt aus 0 erzeugter Thread druckt aus 1 Hauptthread druckt aus 1 erzeugter Thread druckt aus 2 Hauptthread druckt aus 2 erzeugter Thread druckt aus 3 erzeugter Thread druckt aus 4
Die join-Methode kann verwendet werden, um sicherzustellen, dass der Programmablauf erst nach dem Abschluss der Unterthread-Verarbeitung gestoppt wird.
Dies ist ein häufiges Szenario:
use std::thread; fn main() { let s = "hello"; let handle = thread::spawn(|| { println!("{}", s); }); handle.join().unwrap(); }
Das Versuchen, Ressourcen der aktuellen Funktion in einer Unterthread zu verwenden, ist sicherlich falsch! Da das Eigentümersystem verhindert, dass solche gefährlichen Situationen auftreten, was die Sicherheit des Eigentümers zerstören würde. Wir können den move-Schlüsselwort der Klammer verwenden, um dies zu behandeln:
use std::thread; fn main() { let s = "hello"; let handle = thread::spawn(move || { println!("{}", s); }); handle.join().unwrap(); }
Ein Hauptwerkzeug zur Implementierung von Nachrichtenübermittlung und parallelen Verarbeitung in Rust ist der Kanal (channel), der aus einem Sender (transmitter) und einem Empfänger (receiver) besteht.
std::sync::mpsc enthält Methoden für Nachrichtenübermittlung:
use std::thread; use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let wert = String::from("hi"); tx.send(wert).unwrap(); }); let empfangen = rx.recv().unwrap(); println!("Empfangen: {}", empfangen); }
Laufender Output:
Empfangen: hi
Die Unterthread hat den Sender des Hauptthreads tx erhalten und hat seine send-Methode aufgerufen, um einen String zu senden, und dann hat der Hauptthread den String über den entsprechenden Empfänger rx empfangen.