Concorrenza in Rust
Rust offre fearless concurrency: il compilatore garantisce l’assenza di data race a compile-time grazie al sistema di ownership e ai trait Send e Sync.
Concorrenza vs Parallelismo
- Concorrenza: più task fanno progressi in modo alternato
- Parallelismo: più task vengono eseguiti simultaneamente su core diversi
Rust supporta entrambi i modelli.
Trait Send e Sync
- Send: un tipo può essere trasferito tra thread (quasi tutti i tipi)
- Sync: un tipo può essere condiviso tra thread tramite riferimenti
// Rc<T> NON è Send né Sync (non thread-safe)
// Arc<T> è sia Send che Sync (thread-safe)
// RefCell<T> è Send ma NON Sync
// Mutex<T> è sia Send che Sync
Thread
use std::thread;
fn main() {
let handle = thread::spawn(|| {
println!("Ciao dal thread!");
});
handle.join().unwrap(); // Attende il completamento
}
Stato Condiviso con Arc + Mutex
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let contatore = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let contatore = Arc::clone(&contatore);
let handle = thread::spawn(move || {
let mut num = contatore.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Risultato: {}", *contatore.lock().unwrap()); // 10
}
Canali (Message Passing)
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("Ciao dal thread!").unwrap();
});
let messaggio = rx.recv().unwrap();
println!("{}", messaggio);
}
Scoped Threads
Thread con scope che possono prendere in prestito dati dal thread principale:
fn main() {
let mut dati = vec![1, 2, 3];
thread::scope(|s| {
s.spawn(|| {
println!("Dati: {:?}", dati); // Borrow immutabile OK
});
});
dati.push(4); // Dopo lo scope, l'ownership torna al main
}
Approcci alla Concorrenza
| Approccio | Quando usarlo |
|---|---|
| Thread + Mutex | Stato condiviso tra pochi thread |
| Canali (mpsc) | Comunicazione produttore-consumatore |
| Async/Await | I/O-bound con molti task concorrenti |
| Rayon (crate) | Parallelismo sui dati (map-reduce) |
// Esempio con rayon per parallelismo sui dati
use rayon::prelude::*;
let somma: i32 = (0..1_000_000)
.into_par_iter()
.map(|x| x * 2)
.sum();
Conclusione
Rust è unico nel garantire la sicurezza della concorrenza a compile-time. Il compilatore impedisce data race, use-after-free e accessi non sincronizzati. Scegli tra thread con stato condiviso, message passing con canali o async/await in base alle esigenze del tuo programma. La garanzia di “fearless concurrency” rende Rust ideale per software multi-threaded ad alte performance.