Leer programmeren met Rust · Les

Scoped threads

Borrowen over threads heen

Les 3 van 413 stappen

Scoped threads is een gratis Leer programmeren met Rust-les op CoddyKit. Dit is les 3 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Leer programmeren met Rust. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Leer programmeren met Rust bevat in totaal 4 lessen.

Het probleem met lenen in threads

Een normale closure voor thread::spawn moet 'static zijn: hij kan geen lokale variabelen lenen, omdat de thread langer kan blijven bestaan dan de functie die eigenaar van die variabelen is. Daarom zie je zo vaak move en Arc.

Threads met een bereik lossen dit op door te garanderen dat elke thread klaar is voordat het bereik eindigt, zodat het veilig wordt om lokale gegevens te lenen.

Waarom spawn 'static nodig heeft

Met thread::spawn kan de gestarte thread blijven draaien nadat main of een andere functie is teruggekeerd. Als de thread een lokale variabele had geleend, kon die variabele worden vernietigd terwijl de thread haar nog gebruikte. Rust verbiedt dit tijdens het compileren en dwingt je om eigendom van de gegevens naar de closure te verplaatsen.

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

Kennismaken met thread::scope

In Rust 1.63 gestabiliseerd: std::thread::scope maakt een bereik waarin threads lokale variabelen kunnen lenen. Het bereik blokkeert totdat alle threads erin klaar zijn, zodat de leningen nooit ongeldig kunnen worden.

Je start een thread met s.spawn(...) en gebruikt daarbij de bereikhandle s in plaats van 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);
}

Lenen zonder move

Binnen thread::scope kun je lokale variabelen via een referentie lezen zonder move. Meerdere threads met een bereik kunnen tegelijkertijd een onveranderlijke lening van dezelfde gegevens delen, net als normale referenties.

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

Werk verdelen over een slice

Een veelgebruikt patroon is een slice opsplitsen en elke thread een deel laten verwerken. Threads met een bereik maken dit eenvoudig, omdat elke thread een deel van de oorspronkelijke slice rechtstreeks kan lenen; klonen is niet nodig.

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

Retourwaarden verzamelen

Net als gewone threads retourneert s.spawn een ScopedJoinHandle. Roep .join() aan om de retourwaarde van de thread te krijgen. Je kunt handles verzamelen en ze na het starten samenvoegen om de resultaten te verzamelen.

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

Veranderlijke leningen vereisen zorgvuldigheid

Twee threads met een bereik kunnen niet tegelijkertijd veranderlijke leningen van dezelfde gegevens vasthouden; dat zou de regels van Rust voor aliasing schenden. Om gedeelde gegevens vanuit meerdere threads te wijzigen, heb je nog steeds een Mutex nodig, maar één thread kan wel een unieke veranderlijke lening van niet-overlappende delen nemen.

Hieronder wijzigt elke thread een afzonderlijke helft van de array via 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);
}

Het bereik voegt threads automatisch samen

Je hoeft niet voor elke thread met een bereik join aan te roepen. Wanneer de closure van scope terugkeert, voegt Rust automatisch alle nog niet samengevoegde threads samen voordat het verdergaat. Daarom blijven leningen gedurende de hele levensduur van de thread geldig.

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

Bereik combineren met gedeelde wijzigingen

Wanneer threads dezelfde waarde moeten wijzigen, combineer je threads met een bereik met een Mutex. Je hebt geen Arc nodig, omdat het bereik threads al toestaat de lokale Mutex rechtstreeks te lenen.

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

Bereik versus spawn: wanneer gebruik je welke

Gebruik threads met een bereik wanneer het werk begrensd is en binnen een functie wordt voltooid, en je stackgegevens wilt lenen zonder Arc of klonen.

Gebruik thread::spawn wanneer een thread langer moet blijven bestaan dan de huidige functie of gedurende de hele levensduur van het programma moet draaien. Threads met een bereik kunnen hun bereik niet verlaten.

Voorbeeld van parallel mappen

Alles samen: een kleine parallelle map die elk element van een vector in een eigen thread omzet terwijl de invoer wordt geleend, en vervolgens de resultaten in de juiste volgorde verzamelt.

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

Korte toets

Toets je begrip van threads met een bereik.

Samenvatting

Je hebt geleerd over threads met een bereik:

  • thread::spawn vereist closures met 'static; threads met een bereik doen dat niet.
  • Met thread::scope kunnen threads lokale variabelen veilig lenen.
  • Het bereik voegt alle threads automatisch samen voordat het terugkeert.
  • Voor veranderlijk delen is nog steeds een Mutex nodig, maar binnen een bereik geen Arc.
  • Gebruik threads met een bereik voor begrensde paralleliteit binnen een functie.
Gratis beginnen

Leer Rust met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
39
Lessen
144

Veelgestelde vragen

Is de les “Scoped threads” gratis?

Ja — je kunt hier op het web alle 3 lessen van het leerpad Leer programmeren met Rust, waaronder “Scoped threads”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus Leer programmeren met Rust bevat in totaal 4 lessen.

Wat leer ik in “Scoped threads”?

Borrowen over threads heen Je oefent met Leer programmeren met Rust door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Leer programmeren met Rust te beginnen?

Ervaring vooraf is niet nodig. Leer programmeren met Rust op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 3 van 4.

Hoe lang duurt de les “Scoped threads”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Leer programmeren met Rust?

Ja. Elke les over Leer programmeren met Rust bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. mpsc-kanalen
  2. State delen met Arc/Mutex
  3. Scoped threads
  4. Crossbeam-kanalen
← Terug naar Leer programmeren met Rust