Thread con ambito
Prestiti tra thread
Thread con ambito è una lezione Learn Rust Coding gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Learn Rust Coding, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Learn Rust Coding include 4 lezioni in totale.
Il problema dei prestiti nei thread
Una closure normale di thread::spawn deve essere 'static: non può prendere in prestito variabili locali, perché il thread potrebbe sopravvivere alla funzione che le possiede. Ecco perché si vedono così spesso move e Arc.
I thread con ambito risolvono il problema garantendo che ogni thread termini prima della fine dell'ambito, rendendo così sicuro prendere in prestito dati locali.
Perché spawn richiede 'static
Con thread::spawn, il thread avviato può continuare a essere eseguito dopo il ritorno di main o di qualsiasi altra funzione. Se avesse preso in prestito una variabile locale, questa potrebbe essere distrutta mentre il thread la sta ancora utilizzando. Rust lo vieta in fase di compilazione, obbligandovi a spostare i dati posseduti nella closure.
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();
}Presentazione di thread::scope
Stabilizzato in Rust 1.63, std::thread::scope crea un ambito in cui i thread possono prendere in prestito variabili locali. L'ambito rimane bloccato finché tutti i thread al suo interno non terminano, quindi i prestiti non possono mai diventare pendenti.
Si avviano i thread con s.spawn(...), usando l'handle dell'ambito s al posto di 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);
}Prendere in prestito senza move
All'interno di thread::scope potete leggere le variabili locali per riferimento senza move. Più thread con ambito possono condividere contemporaneamente un prestito immutabile degli stessi dati, proprio come normali riferimenti.
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);
}Suddividere il lavoro su una slice
Un modello comune consiste nel suddividere una slice e lasciare che ogni thread elabori una porzione. I thread con ambito rendono questa operazione semplice, perché ogni thread può prendere direttamente in prestito una parte della slice originale, senza bisogno di clonarla.
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);
});
});
}Raccogliere i valori restituiti
Come i thread normali, s.spawn restituisce un ScopedJoinHandle. Chiamate .join() per ottenere il valore restituito dal thread. Potete raccogliere gli handle e attenderne la terminazione dopo aver avviato i thread, così da raccogliere i risultati.
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);
});
}I prestiti mutabili richiedono attenzione
Due thread con ambito non possono mantenere contemporaneamente prestiti mutabili degli stessi dati: ciò violerebbe le regole di aliasing di Rust. Per modificare dati condivisi da più thread serve comunque un Mutex, ma un singolo thread può ottenere un prestito mutabile esclusivo di parti disgiunte.
Di seguito, ogni thread modifica una metà distinta dell'array tramite 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);
}L'ambito attende automaticamente i thread
Non dovete chiamare join su ogni thread con ambito. Quando la closure di scope restituisce il controllo, Rust attende automaticamente tutti i thread non ancora attesi prima di proseguire. Per questo i prestiti sono garantiti validi per tutta la durata del thread.
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");
}Combinare l'ambito con la modifica condivisa
Quando i thread devono modificare lo stesso valore, combinate i thread con ambito con un Mutex. Potete evitare Arc perché l'ambito consente già ai thread di prendere direttamente in prestito il Mutex locale.
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());
}Thread con ambito o avviati: quale usare
Usate i thread con ambito quando il lavoro è delimitato e termina all'interno di una funzione, e volete prendere in prestito dati nello stack senza Arc né clonazioni.
Usate thread::spawn quando un thread deve sopravvivere alla funzione corrente o essere eseguito per tutta la durata del programma. I thread con ambito non possono uscire dal proprio ambito.
Esempio di map parallela
Mettiamo insieme i concetti: una piccola map parallela che trasforma ogni elemento di un vettore nel proprio thread prendendo in prestito l'input, quindi raccoglie i risultati nell'ordine corretto.
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));
}Verifica rapida
Verificate la vostra comprensione dei thread con ambito.
Riepilogo
Avete imparato a conoscere i thread con ambito:
thread::spawnrichiede closure'static; i thread con ambito no.thread::scopeconsente ai thread di prendere in prestito in sicurezza le variabili locali.- L'ambito attende automaticamente tutti i thread prima di restituire il controllo.
- La condivisione mutabile richiede comunque un
Mutex, ma non unArcall'interno di un ambito. - Usate i thread con ambito per il parallelismo delimitato e locale a una funzione.
Domande Frequenti
La lezione «Thread con ambito» è gratuita?
Sì — il testo completo di «Thread con ambito» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Learn Rust Coding, passa a CoddyKit PRO. Il corso Learn Rust Coding include 4 lezioni in totale.
Cosa imparerò in «Thread con ambito»?
Prestiti tra thread Eserciti Learn Rust Coding con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Learn Rust Coding?
Non è richiesta alcuna esperienza precedente. Learn Rust Coding su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.
Quanto tempo richiede la lezione «Thread con ambito»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Learn Rust Coding?
Sì. Ogni lezione Learn Rust Coding include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Canali mpsc
- Condivisione dello stato con Arc/Mutex
- Thread con ambito
- Canali Crossbeam