Concorrenza in Rust — Thread Arc Mutex e Async/Await con Tokio: Codice che Non si Inchioda
> cd .. / HUB_EDITORIALE > Visualizza in Inglese
Sviluppo di siti web

Concorrenza in Rust — Thread Arc Mutex e Async/Await con Tokio: Codice che Non si Inchioda

[2026-07-24] Author: Ing. Calogero Bono
> condividi
Zenithby Meteora Web Il sistema operativo della tua attività. Social, clienti, prenotazioni e fatture in un'unica piattaforma. Palestre, barber, professionisti. Scopri Zenith Demo gratis · senza carta

Il tuo programma Rust multi-thread si blocca senza motivo? Le race condition ti fanno perdere notti di sonno? Non sei solo. Noi, di Meteora Web, ci occupiamo ogni giorno di performance e affidabilità nei progetti che seguiamo — e abbiamo visto troppi sviluppatori affrontare la concorrenza in Rust con l'atteggiamento sbagliato: "butto giù thread e poi vedo". In Rust, la concorrenza non è un'aggiunta opzionale: è un pilastro del linguaggio, e se non la capisci a fondo, il compilatore te lo fa pagare (per fortuna). In questa guida vediamo come padroneggiare thread, Arc, Mutex e async/await con Tokio, con codice funzionante e ragionamenti concreti.

Come funziona la concorrenza con thread Arc e Mutex in Rust?

Rust tratta la concorrenza come un'estensione del sistema di ownership. Un thread può prendere possesso di un valore, ma se più thread devono condividere lo stesso dato, serve un riferimento condiviso e protetto. È qui che entrano in gioco Arc (Atomic Reference Counting) e Mutex (Mutual Exclusion).

Il problema della condivisione tra thread

Immagina di avere un contatore che deve essere incrementato da più thread. In C o Python usi un lock. In Rust, se provi a passare un &mut a più thread, il compilatore urla: "non puoi condividere un riferimento mutabile tra thread senza sincronizzazione". La soluzione standard è Arc>:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Risultato: {}", *counter.lock().unwrap());
}

Spiegazione: Arc permette a più thread di possedere una copia del riferimento. Mutex garantisce che solo un thread alla volta acceda al dato. La lock() restituisce un MutexGuard che rilascia automaticamente il lock quando esce di scope. Fallisce solo se il thread che teneva il lock è andato in panico — da qui l'unwrap() che in produzione va gestito.

Sponsored Protocol

Errori comuni con Arc e Mutex

  • Deadlock: Se un thread acquisisce due mutex in ordine diverso, può bloccarsi. Usa sempre lo stesso ordine o un lock globale.
  • MutexGuard trattenuto attraverso .await: In codice async, non tenere un lock attraverso un punto di sospensione — blocca l'intero runtime.
  • Arc::clone() fuori dal loop: Clona solo quando serve, non prima della creazione del thread.

Un caso reale: seguivamo un cliente con un sistema di elaborazione ordini in Rust. I thread si bloccavano perché avevano un lock su un Mutex che conteneva un database connection pool. La soluzione? Separare il pool in un canale (crossbeam_channel) invece di condividerlo con Mutex.

Quando usare async/await con Tokio invece dei thread?

Non tutta la concorrenza è uguale. I thread fisici sono pesanti: ogni thread ha un stack di almeno 2 MB. In Rust, se devi gestire migliaia di operazioni I/O-bound (HTTP request, letture file, query DB), conviene l'asincrono. Tokio è il runtime più diffuso per async/await in Rust.

Sponsored Protocol

Il modello a eventi di Tokio

Con Tokio non crei thread, ma task leggeri che girano su un pool di thread del runtime. Un task può sospendersi quando aspetta una rete, permettendo ad altri task di usare lo stesso thread. Risultato: migliaia di connessioni simultanee con poche decine di thread.

use tokio::task;

#[tokio::main]
async fn main() {
    let mut handles = vec![];
    for i in 0..100 {
        handles.push(task::spawn(async move {
            println!("Task {} eseguito", i);
            // Simula una operazione I/O
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
        }));
    }

    for handle in handles {
        handle.await.unwrap();
    }
    println!("Tutti i task completati");
}

Il #[tokio::main] macro avvia il runtime. task::spawn crea un task asincrono. await attende il completamento. Nota: non usare std::thread::sleep in un contesto async — blocca il thread, non sospende il task. Usa sempre tokio::time::sleep.

Condivisione di stato in contesto async

Per condividere dati tra task asincroni, non usare Arc come sopra — meglio tokio::sync::Mutex o canali. Perché? Perché std::sync::Mutex blocca il thread, mentre tokio::sync::Mutex sospende il task se il lock è occupato. In un runtime asincrono, vuoi sospendere, non bloccare.

use tokio::sync::Mutex;
use std::sync::Arc;

#[tokio::main]
async fn main() {
    let data = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let data = Arc::clone(&data);
        handles.push(tokio::spawn(async move {
            let mut val = data.lock().await;
            *val += 1;
        }));
    }

    for handle in handles {
        handle.await.unwrap();
    }

    println!("Valore finale: {}", *data.lock().await);
}

Nota l'uso di .lock().await invece di .lock().unwrap(). Questo lock è asincrono: se il lock è conteso, il task si sospende e lascia spazio ad altri task.

Sponsored Protocol

Come combinare thread e async/await in Rust senza perdere il controllo?

A volte hai un misto: operazioni CPU-bound (che vanno su thread separati) e I/O-bound (asincrone). In Rust puoi convivere, ma attenzione: se lanci un task CPU-heavy su un thread di Tokio, blocchi tutto il runtime. La soluzione è spawn_blocking per eseguire lavoro bloccante su un thread pool dedicato.

use tokio::task;

#[tokio::main]
async fn main() {
    // Operazione CPU-bound su thread separato
    let result = task::spawn_blocking(|| {
        let mut sum = 0u64;
        for i in 0..1_000_000 {
            sum += i;
        }
        sum
    }).await.unwrap();

    println!("Somma: {}", result);
}

spawn_blocking restituisce un task asincrono, ma esegue la closure su un thread del pool bloccante, non sul runtime principale. In pratica: per operazioni lunghe senza I/O (calcolo, crittografia), usa thread separati con std::thread o spawn_blocking. Per networking, database, file, usa async con Tokio.

Comunicazione tra thread e async

Per far dialogare thread standard e task asincroni, usa canali. Tokio offre mpsc e oneshot canali asincroni, ma puoi anche usare std::sync::mpsc con attenzione. Un pattern comune: thread produttore invia dati su un canale a un task asincrono consumer.

Sponsored Protocol

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

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(32);

    // Thread produttore (bloccante)
    thread::spawn(move || {
        for i in 0..10 {
            // Invia in modo bloccante ma va bene perché il canale è asincrono
            // Usiamo block_on? No, meglio usare tx.blocking_send()
            tx.blocking_send(i).unwrap();
        }
    });

    // Task consumer asincrono
    while let Some(val) = rx.recv().await {
        println!("Ricevuto: {}", val);
    }
}

Attenzione: tx.blocking_send è disponibile solo quando il canale è stato creato con l'attributo futures o in tokio::sync? In realtà tokio::sync::mpsc::Sender ha il metodo blocking_send per uso in thread bloccanti. Funziona perché internamente usa un lock bloccante. È il modo corretto per mandare dati da thread a async.

Quali sono gli errori comuni nella concorrenza Rust e come evitarli?

Ne abbiamo visti tanti nei progetti che ci arrivano. Ecco i peggiori.

Deadlock da Mutex mantenuto attraverso .await

Se in un contesto async usi std::sync::Mutex e tieni il lock mentre fai .await su qualcosa, blocchi l'intero thread del runtime — deadlock sicuro. Regola: in codice async usa solo tokio::sync::Mutex se devi trattenere il lock attraverso un await, ma idealmente evita del tutto di tenere lock durante attese. Preferisci canali o strutture dati lock-free.

Panico su lock fallito

Spesso si vede lock().unwrap(). In produzione, un thread che ha avuto panico mentre teneva un lock corrompe il Mutex: qualsiasi altro thread che prova a fare lock andrà in panico. Meglio gestire l'errore o usare lock().expect("...") con messaggio chiaro, ma ancora meglio evitare che un thread vada in panico tenendo un lock.

Sponsored Protocol

Arc invece di Arc> per dati mutabili

Errore classico: si usa Arc> per condividere tra thread. RefCell non è Send, perché non è thread-safe. Il compilatore rifiuta. Usa solo Mutex o RwLock per mutabilità condivisa tra thread.

Troppi thread in pool di Tokio

Di default Tokio usa un thread per core CPU. Se spawni 1000 task CPU-bound su Tokio, non guadagni nulla, anzi perdi per context switch. Per lavoro CPU parallelo, usa un pool di thread separato con rayon o spawn_blocking. Noi, di Meteora Web, abbiamo ottimizzato un sistema di importazione dati per un cliente: siamo passati da 30 thread manuali a 4 thread con async + rayon, dimezzando i tempi.

In sintesi

  • Per condividere dati tra thread: Arc> è il pane quotidiano. Ricorda: lock brevi, mai attraverso un await.
  • Per I/O massivo: usa async/await con Tokio. I task sono leggeri, il runtime gestisce il scheduling.
  • Per lavoro CPU-heavy: non farlo su Tokio. Usa spawn_blocking o thread standard con canali.
  • Per comunicare: canali asincroni (tokio::sync::mpsc) sono la scelta più pulita.
  • Non tenere mai un lock standard attraverso un .await. Se devi, usa tokio::sync::Mutex, ma meglio riprogettare.

Pronto a scrivere concorrenza Rust senza dolore? Inizia dal repository ufficiale di Tokio: Tokio Tutorial. E se hai progetti che richiedono performance e robustezza, torna al pillar su Rust per il quadro completo.

> condividi
Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere informatico, fondatore di Meteora Web e Zenith OS. System administrator e progettista di piattaforme, app e CMS proprietari, con esperienza in sviluppo full-stack, marketing digitale ed ecosistema Google.
[ Read Full Dossier ]

> METEORA_WEB // WEB AGENCY

Costruiamo la presenza digitale che la tua azienda merita.

Siti web, social, pubblicità online, e-commerce e hosting performante: ingegnerizzati con metodo da ingegneri informatici a Sciacca, per tutta Italia.

> MW_JOURNAL

> READ_ALL()