Fearless Concurrency
Powered by

> whoami michele.damico@gmail.com https://github.com/la10736 https://www.linkedin.com/in/damico/ @PhenoCoder
Concorrenza
- Sfruttare/Usare meglio le risorse a disposizione
- Organizzare meglio il codice
Ma ...
- Difficile
- Problemi difficilmente riproducibili
- Debugging molto complesso
La promessa di Rust:
Fearless Concurrency
- Evidenziare i problemi di concorrenza in compilazione
- Passare al multi threading senza introdurre regressioni
- Rifattorizzare senza introdurre problemi nuovi

Come?
- Ownership
- Type System
- Mutabilità per istanza
- Lifetime
Sono ottimi strumenti per garantire in fase di compilazione un accesso sicuro alla memoria ....
Sono ottimi strumenti per garantire in fase di compilazione un accesso sicuro e concorrente alla memoria!
Le funzioni di base
- Creare un thread
- Channel
- Condivisione di memoria
-
spawn
std::thread
std::sync::mpsc
let (tx, rx) = std::sync::mpsc::channel();
tx.send("hi").unwrap();
assert_eq!("hi", rx.recv().unwrap());- lock()
- try_lock()
std::sync::Mutex
Le funzioni di base (cont)
std::sync::
- Arc Weak
- RwLock
- Once
- atomic::Atomic*
- Condvar
- Barrier
Le funzioni di base (cont)
Le trait della concorrenza
Send
Sync
inviabile in modo sicuro
condivisibile in modo sicuro
il riferimento è inviabile in modo sicuro
Creare un Thread
std::thread::spawn()
- Crea un nuovo thread che esegue la closure passata
- Ritorna un JoinHandle<T>
- Gestisce valori di ritorno del thread
- I permette di gestire con eleganza gli errori nel thread
let handle = std::thread::spawn(|| {
println!("Hi from thread")
});
handle.join().unwrap();
handle: std::thread::JoinHandle<()>
Creare un Thread (cont)
Valori di ritorno: Quando si esegue la join viene tornato il risultato della closure a meno di errori
let handle= std::thread::spawn(|| {
"hi from Rust!"
});
assert_eq!("hi from Rust!", handle.join().unwrap());
let handle= std::thread::spawn(|| {
42
});
assert_eq!(42, handle.join().unwrap());
handle: std::thread::JoinHandle<&str>
handle: std::thread::JoinHandle<i32>
Creare un Thread (cont)
Gestione degli errori: un panic nel thread non influenza il resto dell' esecuzione ma viene catturato dal JoinHandle.
let handle= std::thread::spawn(|| {
"aa".parse::<i32>().unwrap()
});
assert!(handle.join().is_err());
println!("I'm done");
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an
`Err` value: ParseIntError { kind: InvalidDigit }',
/checkout/src/libcore/result.rs:906:4
note: Run with `RUST_BACKTRACE=1` for a backtrace.
I'm done
Process finished with exit code 0
Channel
std::sync::mpsc::channel()
- Crea un canale per trasferire gli oggetti
- ritorna un Sender<T> e Receiver<T>
- send() e recv() consumano e tornano i valori: ownership
- I Sender<T> possono essere clonati
- I Receiver<T> possono essere trasformati in iteratori
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || tx.send(42).unwrap());
assert_eq!(42, rx.recv().unwrap());tx: std::sync::mpsc::Sender<i32>
rx: std::sync::mpsc::Receiver<i32>
Channel
Ownership
- Quando si esegue una send() si perde la ownership
- Con recv() si acquisisce la ownership
- Quindi i channel letteralmente spostano oggetti
let (tx, rx) = std::sync::mpsc::channel();
let obj = "Object".to_string();
tx.send(obj);
println!("{}", obj);
--> src/main.rs:7:20
|
5 | tx.send(obj);
| --- value moved here
6 |
7 | println!("{}", obj);
| ^^^ value used here after move
|
= note: move occurs because `obj` has type `std::string::String`,
which does not implement the `Copy` trait
Channel (cont)
Multiple Producers
let (tx, rx) = std::sync::mpsc::channel();
for i in 1..10 {
let producer = tx.clone();
std::thread::spawn(move || producer.send(i).unwrap());
}
drop(tx);
while let Ok(x) = rx.recv() {
print!("{} ", x);
}E' Possibile clonare il Sender<T> per ottenere più sorgenti collegate allo stesso consumer.
4 3 2 7 1 6 8 9 5
Process finished with exit code 0Channel (cont)
Receiver come iteratori
let (tx, rx) = std::sync::mpsc::channel();
for i in 1..10 {
let producer = tx.clone();
std::thread::spawn(move || producer.send(i).unwrap());
}
drop(tx);
for i in rx.into_iter() {
print!("{} ", i);
}
E' possibile trasformare il Receiver<T> in un iteratore e ricevere i dati iterando.
1 4 3 6 5 2 7 8 9
Process finished with exit code 0Mutex
std::sync::Mutex
- Il Mutex<T> prende la ownership dell'oggetto
- lock() rende uno smart pointer che garantisce un accesso esclusivo (quindi anche mutabile)
- Quando lo smart pointer viene distrutto la risorsa viene svincolata
let data = std::sync::Mutex::new(String::new());
println!("START");
{
let mut buffer = data.lock().unwrap();
buffer.push_str("Message: ");
}
println!("FIRST");
{
let mut buffer = data.lock().unwrap();
buffer.push_str("Hello!");
}
println!("SECOND");
let content = data.lock().unwrap();
assert_eq!("Message: Hello!", *content);
println!("DONE! -> {}", *content)
START
FIRST
SECOND
DONE! -> Message: Hello!
Provate a togliere un po di parentesi e guardate cosa succede
Grazie!

Fearless Concurrency
By Michele D'Amico
Fearless Concurrency
Introduzione alla concorrenza in Rust
- 290