Concorrenza Estensibile con Send
e Sync
È interessante notare che quasi tutte le funzioni di concorrenza di cui abbiamo parlato finora in questo capitolo fanno parte della libreria standard, non del linguaggio stesso. Le opzioni per gestire la concorrenza non sono limitate al linguaggio o alla libreria standard; puoi scrivere le tue funzioni di concorrenza o usare quelle scritte da altri.
Tuttavia, tra i concetti chiave della concorrenza che sono incorporati nel
linguaggio piuttosto che nella libreria standard ci sono i tratti
std::marker
, Send
e Sync
.
Trasferire Ownership tra Thread
Il trait marcatore Send
indica che la ownership dei valori del type che
implementa Send
può essere trasferita tra i thread. Quasi tutti i type di
Rust implementano Send
, ma ci sono alcune eccezioni, tra cui Rc<T>
: questo
non può implementare Send
perché se si clona un valore Rc<T>
e si cerca di
trasferire la ownership del clone a un altro thread, entrambi i thread
potrebbero aggiornare il conteggio dei reference allo stesso tempo. Per questo
motivo, Rc<T>
è implementato per essere utilizzato in situazioni a thread
singolo in cui non si vuole pagare una penalizzazione in prestazioni rispetto ad
una maggiore sicurezza.
Pertanto, il sistema dei type di Rust e i vincoli di trait assicurano che
non si possa mai inviare accidentalmente un valore Rc<T>
tra i thread in
modo non sicuro. Quando abbiamo provato a farlo nel Listato 16-14, abbiamo
ottenuto l’errore the trait `Send` is not implemented for `Rc<Mutex<i32>>
.
Quando siamo passati ad Arc<T>
, che implementa Send
, il codice è stato
compilato.
Qualsiasi type composto interamente da type che implementano Send
,
anch’esso implementerà automaticamente il trait Send
. Quasi tutti i type
primitivi implementano Send
, a parte i puntatori grezzi, di cui parleremo nel
Capitolo 20.
Accedere da Più Thread
Il trait marcatore Sync
indica che il type che implementa Sync
può
essere referenziato da più thread. In altre parole, qualsiasi type T
implementa Sync
se &T
(un reference immutabile a T
) implementa Send
,
il che significa che il reference può essere inviato in modo sicuro a un altro
thread. Analogamente a Send
, i type primitivi implementano tutti Sync
e
i type composti interamente da type che implementano Sync
, anch’essi
implementano Sync
.
Il puntatore intelligente Rc<T>
non implementa neanche Sync
per le stesse
ragioni per cui non implementa Send
. Il type RefCell<T>
(di cui abbiamo
parlato nel Capitolo 15) e la famiglia correlata di type Cell<T>
non
implementano Sync
. L’implementazione del controllo dei prestiti che
RefCell<T>
fa durante l’esecuzione non è sicura per l’uso coi thread. Invece
il puntatore intelligente Mutex<T>
implementa Sync
e può essere utilizzato
per condividere l’accesso con più thread, come hai visto in “Condividere
Accesso a Mutex<T>
”.
Implementare Manualmente Send
e Sync
È Insicuro
Poiché i type composti interamente da altri type che implementano i trait
Send
e Sync
implementano automaticamente anche Send
e Sync
, non dobbiamo
implementare questi trait manualmente. Come trait marcatori, non hanno
nemmeno metodi da implementare. Sono solo utili per far rispettare gli
invarianti relativi alla concorrenza.
L’implementazione manuale di questi trait comporta l’implementazione di codice
Rust insicuro. Parleremo dell’utilizzo di codice Rust insicuro (Unsafe Rust)
nel Capitolo 20; per ora, l’informazione importante è che la creazione di nuovi
type concorrenti non costituiti da parti Send
e Sync
richiede un’attenta
riflessione per mantenere le garanzie di sicurezza. “The
Rustonomicon” contiene maggiori informazioni su queste garanzie e su
come rispettarle.
Riepilogo
Non è l’ultima volta che vedrai la concorrenza in questo libro: il prossimo capitolo si concentra sulla programmazione asincrona e il progetto del Capitolo 21 utilizzerà i concetti di questo capitolo in una situazione più realistica rispetto agli esempi minori discussi qui.
Come accennato in precedenza, dato che la gestione della concorrenza in Rust fa parte del linguaggio solo in minima parte, molte soluzioni per la concorrenza sono implementate sotto forma di crate. Questi si evolvono più rapidamente rispetto alla libreria standard, quindi assicurati di cercare online i crate più aggiornati e all’avanguardia da utilizzare in situazioni in cui necessiti di elaborazioni multi-thread.
La libreria standard di Rust fornisce canali per il passaggio di messaggi e i
puntatori intelligenti, come Mutex<T>
e Arc<T>
, che sono sicuri da usare in
contesti concorrenti. Il sistema dei type e il controllo di prestiti
assicurano che il codice che utilizza queste soluzioni non finisca con accessi
ai dati conflittuali o riferimenti non validi. Una volta che avrai compilato il
tuo codice, potrai essere certo che verrà eseguito felicemente su più thread
senza i tipi di bug difficili da rintracciare comuni in altri linguaggi. La
programmazione concorrente non è più un concetto di cui aver paura: vai avanti e
rendi i tuoi programmi concorrenti, senza paura!