0Pricing
Learn Rust Coding · Lektion

Bereichsbegrenzte Threads

Über Threads hinweg borgen

Bereichsbegrenzte Threads ist eine kostenlose Learn Rust Coding-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Learn Rust Coding-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Learn Rust Coding-Kurs umfasst insgesamt 4 Lektionen.

Das Problem mit Borrowing in Threads

Eine normale thread::spawn-Closure muss 'static sein: Sie darf keine lokalen Variablen ausleihen, da der Thread länger leben könnte als die Funktion, der diese Variablen gehören. Deshalb sehen Sie so häufig move und Arc.

Scoped Threads lösen dieses Problem, indem sie garantieren, dass jeder Thread beendet ist, bevor der Gültigkeitsbereich endet. Dadurch können lokale Daten sicher ausgeliehen werden.

Warum spawn 'static benötigt

Bei thread::spawn kann der gestartete Thread weiterlaufen, nachdem main oder eine beliebige Funktion zurückgekehrt ist. Wenn er eine lokale Variable ausgeliehen hätte, könnte diese Variable zerstört werden, während der Thread sie noch verwendet. Rust verbietet dies bereits zur Kompilierzeit und zwingt Sie, eigene Daten in die Closure zu verschieben.

use std::thread;

fn main() {
    let nums = vec![1, 2, 3];
    // move transfers ownership into the thread
    let handle = thread::spawn(move || {
        println!("in thread: {:?}", nums);
    });
    handle.join().unwrap();
}

thread::scope kennenlernen

Das in Rust 1.63 stabilisierte std::thread::scope erstellt einen Gültigkeitsbereich, in dem Threads lokale Variablen ausleihen können. Der Gültigkeitsbereich blockiert, bis alle darin gestarteten Threads abgeschlossen sind, sodass die Borrows niemals ungültig werden können.

Sie starten Threads mit s.spawn(...) und verwenden dabei das Scope-Handle s anstelle von thread::spawn.

use std::thread;

fn main() {
    let data = vec![10, 20, 30];
    thread::scope(|s| {
        s.spawn(|| {
            println!("borrowed: {:?}", data);
        });
    });
    // data is still usable here
    println!("after scope: {:?}", data);
}

Borrowing ohne move

Innerhalb von thread::scope können Sie lokale Variablen per Referenz lesen, ohne move zu verwenden. Mehrere Scoped Threads können gleichzeitig einen unveränderlichen Borrow derselben Daten teilen, genau wie normale Referenzen.

use std::thread;

fn main() {
    let message = String::from("shared text");
    thread::scope(|s| {
        s.spawn(|| println!("thread 1 sees: {}", message));
        s.spawn(|| println!("thread 2 sees: {}", message));
    });
    println!("main still owns: {}", message);
}

Arbeit auf einen Slice aufteilen

Ein häufiges Muster besteht darin, einen Slice aufzuteilen und jeden Thread einen Teil davon verarbeiten zu lassen. Scoped Threads machen dies übersichtlich, da jeder Thread direkt einen Teil des ursprünglichen Slices ausleihen kann, ohne dass Klonen erforderlich ist.

use std::thread;

fn main() {
    let numbers = [1, 2, 3, 4, 5, 6];
    let (left, right) = numbers.split_at(3);
    thread::scope(|s| {
        s.spawn(|| {
            let sum: i32 = left.iter().sum();
            println!("left sum:  {}", sum);
        });
        s.spawn(|| {
            let sum: i32 = right.iter().sum();
            println!("right sum: {}", sum);
        });
    });
}

Rückgabewerte sammeln

Wie reguläre Threads gibt s.spawn einen ScopedJoinHandle zurück. Rufen Sie .join() auf, um den Rückgabewert des Threads zu erhalten. Sie können die Handles sammeln und nach dem Start aller Threads mit ihnen join aufrufen, um die Ergebnisse zusammenzutragen.

use std::thread;

fn main() {
    let inputs = [2, 4, 6];
    let mut handles = vec![];
    thread::scope(|s| {
        for &x in &inputs {
            handles.push(s.spawn(move || x * x));
        }
        let results: Vec<i32> = handles.into_iter()
            .map(|h| h.join().unwrap())
            .collect();
        println!("{:?}", results);
    });
}

Veränderbare Borrows erfordern Vorsicht

Zwei Scoped Threads können nicht gleichzeitig veränderbare Borrows derselben Daten halten; das würde die Aliasing-Regeln von Rust verletzen. Um gemeinsam genutzte Daten aus mehreren Threads zu verändern, benötigen Sie weiterhin einen Mutex. Ein einzelner Thread kann jedoch einen eindeutigen veränderbaren Borrow disjunkter Teile übernehmen.

Im Folgenden verändert jeder Thread eine eigene Hälfte des Arrays über split_at_mut.

use std::thread;

fn main() {
    let mut data = [1, 2, 3, 4];
    let (a, b) = data.split_at_mut(2);
    thread::scope(|s| {
        s.spawn(|| { for x in a.iter_mut() { *x *= 10; } });
        s.spawn(|| { for x in b.iter_mut() { *x += 100; } });
    });
    println!("{:?}", data);
}

Der Scope führt automatisch join aus

Sie müssen nicht für jeden Scoped Thread join aufrufen. Wenn die scope-Closure zurückkehrt, führt Rust automatisch für alle noch nicht verbundenen Threads join aus, bevor die Ausführung fortgesetzt wird. Deshalb sind Borrows für die gesamte Lebensdauer des Threads garantiert gültig.

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

fn main() {
    let label = String::from("task");
    thread::scope(|s| {
        s.spawn(|| {
            thread::sleep(Duration::from_millis(30));
            println!("{} done", label);
        });
        println!("spawned, scope will wait");
    });
    println!("all scoped threads finished");
}

Scope mit gemeinsam genutzten Änderungen kombinieren

Wenn Threads denselben Wert verändern müssen, kombinieren Sie Scoped Threads mit einem Mutex. Auf Arc können Sie verzichten, da der Scope es den Threads bereits ermöglicht, den lokalen Mutex direkt auszuleihen.

use std::sync::Mutex;
use std::thread;

fn main() {
    let counter = Mutex::new(0);
    thread::scope(|s| {
        for _ in 0..5 {
            s.spawn(|| {
                let mut n = counter.lock().unwrap();
                *n += 1;
            });
        }
    });
    println!("counter = {}", *counter.lock().unwrap());
}

Scoped und gestartete Threads: Wann welches verwenden

Verwenden Sie Scoped Threads, wenn die Arbeit begrenzt ist und innerhalb einer Funktion abgeschlossen wird und Sie Stack-Daten ohne Arc oder Klonen ausleihen möchten.

Verwenden Sie thread::spawn, wenn ein Thread länger als die aktuelle Funktion leben oder während der gesamten Programmlaufzeit ausgeführt werden muss. Scoped Threads können ihren Scope nicht verlassen.

Beispiel für eine parallele Map

Alles zusammengeführt: Eine kleine parallele Map transformiert jedes Element eines Vektors in einem eigenen Thread, während sie die Eingabe ausleiht, und sammelt anschließend die Ergebnisse in der richtigen Reihenfolge.

use std::thread;

fn parallel_double(items: &[i32]) -> Vec<i32> {
    let mut handles = Vec::new();
    let mut out = Vec::new();
    thread::scope(|s| {
        for &x in items {
            handles.push(s.spawn(move || x * 2));
        }
        for h in handles {
            out.push(h.join().unwrap());
        }
    });
    out
}

fn main() {
    let nums = vec![1, 2, 3, 4];
    println!("{:?}", parallel_double(&nums));
}

Kurzer Test

Testen Sie Ihr Verständnis von Scoped Threads.

Zusammenfassung

Sie haben Scoped Threads kennengelernt:

  • thread::spawn erfordert Closures mit 'static; Scoped Threads nicht.
  • thread::scope ermöglicht es Threads, lokale Variablen sicher auszuleihen.
  • Der Scope führt vor der Rückkehr automatisch für alle Threads join aus.
  • Gemeinsame veränderbare Nutzung erfordert weiterhin einen Mutex, innerhalb eines Scopes jedoch keinen Arc.
  • Verwenden Sie Scoped Threads für begrenzte Parallelität innerhalb einer Funktion.

Häufig gestellte Fragen

Ist die Lektion „Bereichsbegrenzte Threads“ kostenlos?

Ja — der vollständige Text von „Bereichsbegrenzte Threads“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Learn Rust Coding-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Learn Rust Coding-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Bereichsbegrenzte Threads“?

Über Threads hinweg borgen Du übst Learn Rust Coding mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um Learn Rust Coding zu starten?

Keine Vorkenntnisse erforderlich. Learn Rust Coding auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.

Wie lange dauert die Lektion „Bereichsbegrenzte Threads“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser Learn Rust Coding-Lektion Code schreiben und ausführen?

Ja. Jede Learn Rust Coding-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. mpsc-Kanäle
  2. State mit Arc/Mutex teilen
  3. Bereichsbegrenzte Threads
  4. Crossbeam-Kanäle
← Zurück zu Learn Rust Coding