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

  1. Evidenziare i problemi di concorrenza in compilazione
  2. Passare al multi threading senza introdurre regressioni
  3. 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 0

Channel (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 0

Mutex

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