Storicamente, la gestione della multithreading è stata accompagnata da crash, race condition e perdite di memoria, soprattutto durante lo scambio di memoria non controllato. Rust implementa il concetto di sicurezza dei thread a livello di tipi: un oggetto può essere passato a un thread solo se implementa i trait necessari (Send, Sync). I thread stessi vengono creati attraverso std::thread::spawn, e la comunicazione tra di essi avviene tramite canali o memoria condivisa con mutazione controllata (Mutex, Arc).
Problema: gestire manualmente la sincronizzazione è complicato e pericoloso. Il passaggio di oggetti arbitrari tra i thread senza un chiaro trasferimento di possesso porta a race condition e crash.
Soluzione: solo oggetti esplicitamente spostabili (move) o condivisi tramite Arc, Mutex, nonché canali di comunicazione incorporati (std::sync::mpsc, crossbeam). Questo minimizza gli errori legati allo scambio di dati sincrono e asincrono: il possesso è sempre univoco.
Esempio di codice:
use std::thread; use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send(String::from("Hello from thread!")).unwrap(); }); let received = rx.recv().unwrap(); println!("Received: {}", received); }
Caratteristiche principali:
È possibile continuare a usare un oggetto nel thread principale dopo averlo passato tramite move?
No, una volta che l'oggetto è stato spostato (ad esempio, in una closure in thread::spawn), non è più possibile utilizzarlo nel thread genitore, il compilatore non consentirà di compilare il codice.
È possibile passare riferimenti mutabili (&mut T) tra i thread?
No, un riferimento mutabile &mut T può esistere solo in una sola istanza, e il trait Send non è implementato per esso per impostazione predefinita. Per lavorare con dati mutabili si utilizza un wrapper tramite Mutex/Arc.
Perché non si può utilizzare Rc<T> per condividere la proprietà tra i thread?
Rc<T> non implementa Sync e Send, poiché il suo contatore interno non è thread-safe. Per la sicurezza dei thread si utilizza Arc<T> (contatore di riferimenti atomici).
// Confronto tra Rc e Arc use std::sync::Arc; let x = Arc::new(5); // può essere clonato e condiviso tra più thread
Uno sviluppatore ha deciso di condividere una stringa tra i thread utilizzando Rc<String>, inserendo Rc all'interno di thread::spawn. Il codice compila solo se forzato tramite unsafe casting, dopo di che l'applicazione potrebbe andare in crash o lavorare con dati danneggiati.
Vantaggi:
Svantaggi:
Si utilizza Arc<String> + Mutex<String> per l'accesso protetto, oppure si passa un messaggio tramite un canale, senza condivisione della proprietà.
Vantaggi:
Svantaggi: