thread::spawn în Rust

Pornește un fir de execuție nou care rulează funcția dată; join() așteaptă terminarea lui și îi dă rezultatul.

Sintaxă
use std::thread;

pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where F: FnOnce() -> T + Send + 'static, T: Send + 'static

fn join(self) -> Result<T>

Ce face thread::spawn

thread::spawn(|| ...) pornește un fir nou care rulează closure-ul în paralel cu firul principal și returnează imediat un JoinHandle<T>. Prin handle.join() aștepți ca firul să termine și primești Ok(valoare), adică ce a returnat closure-ul, sau Err dacă firul a intrat în panică.

Closure-ul trebuie să fie 'static: nu poate împrumuta variabile locale ale lui main, pentru că firul ar putea trăi mai mult decât ele. De aceea se scrie move || ..., care mută variabilele în fir. Pentru împrumuturi există thread::scope.

Când main se termină, programul se oprește, chiar dacă alte fire încă rulează. Fă join pe fiecare fir dacă vrei să-și termine treaba. Ordinea în care rulează firele nu este garantată.

Parametri

Parametru Descriere
f Funcția (closure-ul) rulată în firul nou. Valoarea returnată ajunge la join.

Ce returnează

spawn: un JoinHandle<T>. join: Result<T, Box<dyn Any + Send>>.

Exemple

Exemplul 1: Calcul în paralel, rezultate prin join

Fiecare fir adună notele unei clase. Firele pot termina în orice ordine, dar noi le așteptăm în ordinea pornirii, deci rezultatul afișat este mereu același.

use std::thread;

fn main() {
    let clase = vec![
        ("9A", vec![9, 8, 10, 7]),
        ("9B", vec![6, 9, 9]),
        ("9C", vec![10, 10, 8, 9, 7]),
    ];

    let mut fire = Vec::new();
    for (nume, note) in clase {
        fire.push(thread::spawn(move || {
            let medie = note.iter().sum::<i32>() as f64 / note.len() as f64;
            (nume, medie)
        }));
    }

    for fir in fire {
        let (nume, medie) = fir.join().unwrap();
        println!("clasa {nume}: media {medie:.2}");
    }
}

Rezultat

clasa 9A: media 8.50
clasa 9B: media 8.00
clasa 9C: media 8.80

Exemplul 2: Un fir care intră în panică

Panica oprește doar firul respectiv; join returnează Err, iar firul principal continuă. Hook-ul de panică este oprit ca rezultatul să nu conțină numărul variabil al firului.

use std::panic;
use std::thread;

fn main() {
    panic::set_hook(Box::new(|_| {}));

    let bun = thread::spawn(|| 10 / 2);
    let rau = thread::spawn(|| {
        let impartitor = "0".parse::<i32>().unwrap();
        10 / impartitor
    });

    println!("firul bun: {:?}", bun.join());
    match rau.join() {
        Ok(v) => println!("firul rău: {v}"),
        Err(_) => println!("firul rău a intrat în panică"),
    }
    println!("main continuă");
}

Rezultat

firul bun: Ok(5)
firul rău a intrat în panică
main continuă

Greșeli frecvente

  • Uiți move: eroarea closure may outlive the current function, but it borrows .... Adaugă move sau folosește thread::scope.
  • Nu faci join: când main se termină, firele sunt oprite, iar o parte din muncă (sau din mesaje) se pierde.
  • Afișezi direct din fire și te aștepți la o ordine fixă: ordinea se schimbă de la o rulare la alta. Returnează valorile și afișează-le după join.

Comenzi înrudite

  • thread::scope: Pornește fire care pot împrumuta variabile locale; scope așteaptă automat terminarea tuturor firelor create în el.
  • Arc: Varianta lui Rc care poate fi împărțită între fire: contorul de proprietari este actualizat atomic.
  • mpsc::channel: Creează un canal prin care firele își trimit mesaje: Sender trimite cu send(), Receiver primește.
  • thread::sleep: Oprește firul curent cel puțin cât durata dată; celelalte fire continuă să ruleze.
  • thread::available_parallelism: Estimează câte fire pot rula cu adevărat în paralel pe calculator (de obicei numărul de nuclee logice).