0Pricing
Learn Rust Coding · Lezione

Condivisione dello stato con Arc/Mutex

Dati condivisi in sicurezza

Condivisione dello stato con Arc/Mutex è una lezione Learn Rust Coding gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Learn Rust Coding, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Learn Rust Coding include 4 lezioni in totale.

Perché lo stato condiviso è difficile

A volte il passaggio di messaggi non è sufficiente e più thread devono davvero leggere e modificare gli stessi dati. Rust non consente di condividere un valore mutabile tra thread senza protezione, perché ciò rischierebbe di causare un data race.

I due strumenti da combinare sono:

  • Arc per la proprietà condivisa tra thread.
  • Mutex per la modifica esclusiva e sicura.

Rc non è thread-safe

Rc fornisce la proprietà condivisa, ma solo all'interno di un singolo thread. Il suo conteggio dei riferimenti non è sincronizzato, quindi il compilatore rifiuta di trasferirlo tra thread. Per la condivisione tra più thread è necessario Arc (Atomically Reference Counted).

Arc si comporta come Rc, ma aggiorna il conteggio con operazioni atomiche, rendendo sicure le copie tra thread.

use std::sync::Arc;

fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    let clone1 = Arc::clone(&data);
    println!("original: {:?}", data);
    println!("clone:    {:?}", clone1);
    println!("count:    {}", Arc::strong_count(&data));
}

Mutex fornisce accesso esclusivo

Un Mutex racchiude i dati e garantisce che vi acceda un solo thread alla volta. Si chiama .lock() per ottenere un MutexGuard, che dereferenzia al valore interno. Il lock viene rilasciato automaticamente quando la guardia esce dall'ambito.

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);
    {
        let mut num = m.lock().unwrap();
        *num += 10;
    } // guard dropped here, lock released
    println!("value = {:?}", m.lock().unwrap());
}

Combinare Arc e Mutex

Per condividere dati mutabili tra i thread, li si racchiude in Arc>:

  • Arc consente a molti thread di possedere un riferimento agli stessi dati.
  • Mutex consente a ogni thread di modificarli in sicurezza, uno alla volta.

Clonate l'Arc per ogni thread prima di avviarlo.

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

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let c = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut n = c.lock().unwrap();
        *n += 1;
    });
    handle.join().unwrap();
    println!("counter = {}", *counter.lock().unwrap());
}

Un contatore condiviso tra molti thread

L'esempio classico: dieci thread incrementano ciascuno un contatore condiviso. Ogni thread possiede la propria copia di Arc e acquisisce il lock del Mutex per aggiungere uno. Dopo aver atteso la terminazione di tutti i thread, il totale è esattamente 10, senza alcuna race condition.

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 c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            let mut num = c.lock().unwrap();
            *num += 1;
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
    println!("Result: {}", *counter.lock().unwrap());
}

L'ambito del lock è importante

Il MutexGuard mantiene il lock finché non viene eliminato. Mantenerlo durante operazioni lente blocca gli altri thread. Mantenete le sezioni critiche brevi: acquisire il lock, modificare, rilasciare. Racchiudere il lock in un piccolo blocco garantisce che venga rilasciato prima di qualsiasi elaborazione aggiuntiva.

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

fn main() {
    let log = Arc::new(Mutex::new(Vec::new()));
    let mut handles = vec![];
    for i in 0..3 {
        let l = Arc::clone(&log);
        handles.push(thread::spawn(move || {
            {
                let mut v = l.lock().unwrap();
                v.push(i);
            } // released quickly
        }));
    }
    for h in handles { h.join().unwrap(); }
    let mut result = log.lock().unwrap().clone();
    result.sort();
    println!("{:?}", result);
}

Deadlock: un rischio concreto

Si verifica un deadlock quando due thread possiedono ciascuno un lock di cui l'altro ha bisogno e rimangono entrambi in attesa per sempre. Rust previene le race condition, ma non i deadlock. Evitateli acquisendo sempre più mutex nello stesso ordine e mantenendo brevi i lock.

Evitate inoltre di acquisire due volte lo stesso Mutex nello stesso thread: il Mutex standard non è rientrante.

Poisoning quando un thread va in panic

Se un thread va in panic mentre mantiene un lock, il Mutex diventa poisoned. Le chiamate successive a .lock() restituiscono Err, così sapete che i dati potrebbero essere incoerenti. Se ritenete che i dati siano ancora utilizzabili, potete recuperare la guardia interna tramite into_inner() sull'errore.

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

fn main() {
    let data = Arc::new(Mutex::new(0));
    let d = Arc::clone(&data);
    let _ = thread::spawn(move || {
        let mut g = d.lock().unwrap();
        *g = 7;
        panic!("boom"); // poisons the mutex
    }).join();
    match data.lock() {
        Ok(g) => println!("ok: {}", *g),
        Err(poisoned) => println!("recovered: {}", *poisoned.into_inner()),
    }
}

RwLock per molti lettori

Quando le letture sono di gran lunga più numerose delle scritture, un RwLock può essere più veloce di un Mutex. Consente molti lettori simultanei oppure un solo scrittore. Usate .read() per l'accesso condiviso e .write() per l'accesso esclusivo.

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

fn main() {
    let config = Arc::new(RwLock::new(String::from("v1")));
    let reader = Arc::clone(&config);
    let r = thread::spawn(move || {
        let val = reader.read().unwrap();
        println!("read: {}", *val);
    });
    r.join().unwrap();
    {
        let mut w = config.write().unwrap();
        *w = String::from("v2");
    }
    println!("final: {}", *config.read().unwrap());
}

Atomics per contatori semplici

Per un singolo contatore intero, un Mutex completo è eccessivo. Tipi come AtomicUsize offrono aggiornamenti senza lock tramite metodi come fetch_add. Racchiudeteli in un Arc per condividerli tra i thread. Scegliete un Ordering; SeqCst è il valore predefinito sicuro più semplice.

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;

fn main() {
    let counter = Arc::new(AtomicUsize::new(0));
    let mut handles = vec![];
    for _ in 0..5 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            c.fetch_add(1, Ordering::SeqCst);
        }));
    }
    for h in handles { h.join().unwrap(); }
    println!("count = {}", counter.load(Ordering::SeqCst));
}

Scegliere lo strumento giusto

Indicazioni rapide per lo stato condiviso:

  • Channel: trasferimento della proprietà, secondo un modello a pipeline.
  • Arc<Mutex>: struttura mutabile condivisa, con letture e scritture miste.
  • Arc<RwLock>: dati condivisi soggetti soprattutto a lettura.
  • Atomics: contatori o flag primitivi singoli.

Preferite lo strumento più semplice adatto al caso; ricorrete ai lock solo quando il message passing non rappresenta bene il problema.

Verifica rapida

Verificate la vostra comprensione dello stato condiviso.

Riepilogo

Avete imparato a condividere lo stato in sicurezza tra i thread:

  • Arc abilita la proprietà condivisa sicura tra i thread; Rc no.
  • Mutex fornisce modifiche esclusive tramite una guardia che rilascia automaticamente il lock.
  • Arc<Mutex<T>> è il modello standard per i dati mutabili condivisi.
  • Mantenete brevi gli ambiti dei lock e prestate attenzione a deadlock e poisoning.
  • RwLock è adatto ai dati soggetti soprattutto a lettura; gli atomici ai contatori semplici.

Domande Frequenti

La lezione «Condivisione dello stato con Arc/Mutex» è gratuita?

Sì — il testo completo di «Condivisione dello stato con Arc/Mutex» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Learn Rust Coding, passa a CoddyKit PRO. Il corso Learn Rust Coding include 4 lezioni in totale.

Cosa imparerò in «Condivisione dello stato con Arc/Mutex»?

Dati condivisi in sicurezza Eserciti Learn Rust Coding con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Learn Rust Coding?

Non è richiesta alcuna esperienza precedente. Learn Rust Coding su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Condivisione dello stato con Arc/Mutex»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Learn Rust Coding?

Sì. Ogni lezione Learn Rust Coding include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Canali mpsc
  2. Condivisione dello stato con Arc/Mutex
  3. Thread con ambito
  4. Canali Crossbeam
← Torna a Learn Rust Coding