saverioriotto.it

Concurrency in Rust: Thread, Canali e Programmazione Asincrona

Esplora la concurrency in Rust! Impara a creare e gestire thread, comunicare in sicurezza con i canali e utilizzare async/await per la programmazione asincrona.

Concurrency in Rust: Thread, Canali e Programmazione Asincrona

Rust offre potenti strumenti per la programmazione concorrente e asincrona. Con un focus sulla sicurezza e sull'assenza di condizioni di gara (race conditions), Rust garantisce un controllo rigoroso sul modo in cui i thread condividono e accedono ai dati. In questo articolo, scoprirai come:

  • Creare e gestire thread.
  • Comunicare tra thread in modo sicuro utilizzando i canali.
  • Introdurre la programmazione asincrona con async/await.

Creare e Gestire Thread

Rust supporta la creazione di thread con la libreria standard attraverso il modulo std::thread. Ogni thread è una linea di esecuzione separata che può eseguire codice in parallelo.

Creazione di un Thread

Ecco un esempio base per creare un thread:

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..5 {
            println!("Esecuzione del thread: {}", i);
        }
    });

    for i in 1..5 {
        println!("Esecuzione del thread principale: {}", i);
    }

    handle.join().unwrap(); // Attende che il thread termini
}

Il metodo thread::spawn avvia un nuovo thread, mentre handle.join() garantisce che il thread principale aspetti il completamento del thread figlio.

Passare Dati ai Thread

Puoi passare dati ai thread utilizzando move closure, che trasferisce la proprietà dei dati al thread:

fn main() {
    let valori = vec![1, 2, 3];

    let handle = thread::spawn(move || {
        println!("Valori trasferiti: {:?}", valori);
    });

    handle.join().unwrap();
}

Il modificatore move è necessario per trasferire la proprietà della variabile valori al thread.

Comunicazione Sicura con i Canali

Rust offre i canali attraverso il modulo std::sync::mpsc (multiple producer, single consumer) per la comunicazione tra thread. I canali garantiscono una trasmissione sicura dei dati.

Esempio Base di Canali

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let messaggio = String::from("Ciao dal thread!");
        tx.send(messaggio).unwrap();
    });

    let ricevuto = rx.recv().unwrap();
    println!("Messaggio ricevuto: {}", ricevuto);
}
  • tx: trasmettitore.
  • rx: ricevitore.
  • send invia dati al canale, e recv li riceve.

Molteplici Produttori

Puoi clonare il trasmettitore per consentire a più thread di inviare dati:

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();
    let tx_clone = tx.clone();

    thread::spawn(move || {
        tx.send("Messaggio dal primo thread").unwrap();
    });

    thread::spawn(move || {
        tx_clone.send("Messaggio dal secondo thread").unwrap();
    });

    for messaggio in rx {
        println!("Ricevuto: {}", messaggio);
    }
}

Introduzione ad Async/Await

Rust supporta la programmazione asincrona attraverso il modello async/await, che consente di scrivere codice non bloccante in modo semplice ed espressivo. Questa funzionalità è abilitata dal modulo async-std o tokio.

Scrivere una Funzione Asincrona

Una funzione dichiarata con async restituisce un future, che rappresenta un valore calcolato in futuro.

async fn saluto() {
    println!("Ciao, mondo asincrono!");
}

Eseguire Funzioni Asincrone

Per eseguire funzioni asincrone, utilizzi un runtime come tokio.

#[tokio::main]
async fn main() {
    saluto().await;
}

Esempio di Download Asincrono

Con reqwest, puoi eseguire operazioni di rete asincrone:

use reqwest;

#[tokio::main]
async fn main() {
    let url = "https://jsonplaceholder.typicode.com/posts/1";
    let risposta = reqwest::get(url).await.unwrap();
    let contenuto = risposta.text().await.unwrap();

    println!("Risposta: {}", contenuto);
}

Esercizi Pratici

  1. Thread e Somma
    Scrivi un programma che utilizzi più thread per calcolare la somma di una serie di numeri.

    Soluzione:

    use std::thread;
    
    fn main() {
        let numeri = vec![1, 2, 3, 4, 5];
        let handle = thread::spawn(move || {
            numeri.iter().sum::()
        });
    
        let somma = handle.join().unwrap();
        println!("Somma: {}", somma);
    }
  2. Canali e Filtro
    Implementa un programma in cui un thread invia numeri e un altro li filtra, inviando solo i pari.

    Soluzione:

    use std::sync::mpsc;
    use std::thread;
    
    fn main() {
        let (tx, rx) = mpsc::channel();
    
        thread::spawn(move || {
            for i in 1..10 {
                tx.send(i).unwrap();
            }
        });
    
        for valore in rx {
            if valore % 2 == 0 {
                println!("Pari: {}", valore);
            }
        }
    }
  3. Download Multiplo Asincrono
    Utilizza async/await per scaricare più URL contemporaneamente.

    Soluzione:

    use reqwest;
    
    #[tokio::main]
    async fn main() {
        let urls = vec![
            "https://jsonplaceholder.typicode.com/posts/1",
            "https://jsonplaceholder.typicode.com/posts/2",
        ];
    
        let futures = urls.iter().map(|&url| reqwest::get(url));
        let risposte = futures::future::join_all(futures).await;
    
        for risposta in risposte {
            println!("Risposta: {}", risposta.unwrap().text().await.unwrap());
        }
    }

Conclusione

La concurrency in Rust è sicura e potente grazie al controllo rigoroso sulla memoria e al supporto nativo per i thread e la programmazione asincrona. Nel prossimo articolo esploreremo la gestione degli arrori con funzioni come Panics, Option e Result. Prova gli esercizi proposti per approfondire i concetti e padroneggiare gli strumenti che Rust offre per la programmazione concorrente!




Commenti
* Obbligatorio