Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

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!