SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI
Concorrenza Estensibile con i Tratti Sync e Send
In modo interessante, il linguaggio Rust ha pochissime caratteristiche di concorrenza. Quasi tutte le funzionalità di concorrenza di cui abbiamo parlato finora in questo capitolo fanno parte della libreria standard, non del linguaggio. Le tue opzioni per gestire la concorrenza non sono limitate al linguaggio o alla libreria standard; puoi scrivere le tue funzionalità di concorrenza o utilizzare quelle scritte da altri.
Tuttavia, due concetti di concorrenza sono integrati nel linguaggio: i tratti marcatore Sync e Send. Consentire il Trasferimento della Proprietà tra Thread con Send
Il tratto marcatore Send indica che la proprietà dei valori del tipo che implementa Send può essere trasferita tra i thread. Quasi ogni tipo di Rust è Send, ma ci sono alcune eccezioni, tra cui Rc<T>: questo non può essere Send perché se clonassi un valore Rc<T> e cercassi di trasferire la proprietà del clone a un altro thread, entrambi i thread potrebbero aggiornare il conteggio dei riferimenti contemporaneamente. Per questo motivo, Rc<T> è implementato per l’uso in situazioni single-threaded dove non vuoi pagare il costo delle prestazioni thread-safe.
Pertanto, il sistema di tipi di Rust e i vincoli dei tratti garantiscono che non puoi mai inviare accidentalmente un valore Rc<T> tra thread in modo non sicuro. Quando abbiamo provato a farlo nel Listato 16-14, abbiamo ottenuto l’errore “il tratto Send non è implementato per Rc<Mutex<i32>>”. Quando abbiamo usato Arc<T>, che è Send, il codice si è compilato.
Qualsiasi tipo composto interamente da tipi Send è automaticamente contrassegnato anche come Send. Quasi tutti i tipi primitivi sono Send, a parte i puntatori raw, di cui parleremo nel Capitolo 19. Consentire l’Accesso da Parte di Più Thread con Sync
Il tratto marcatore Sync indica che è sicuro per il tipo che implementa Sync essere referenziato da più thread. In altre parole, qualsiasi tipo T è Sync se &T (un riferimento immutabile a T) è Send, il che significa che il riferimento può essere inviato in modo sicuro a un altro thread. Similmente a Send, i tipi primitivi sono Sync, e i tipi composti interamente da tipi che sono Sync sono anche Sync.
Il puntatore intelligente Rc<T> non è Sync per gli stessi motivi per cui non è Send. Il tipo RefCell<T> (di cui abbiamo parlato nel Capitolo 15) e la famiglia di tipi Cell<T> correlati non sono Sync. L’implementazione del controllo dei mutui che RefCell<T> esegue a runtime non è thread-safe. Il puntatore intelligente Mutex<T> è Sync e può essere utilizzato per condividere l’accesso con più thread come hai visto nella sezione “Condivisione di un Mutex<T> tra Più Thread”. Implementare Send e Sync Manualmente non è Sicuro
Poiché i tipi composti da tratti Send e Sync sono automaticamente anche Send e Sync, non dobbiamo implementare manualmente quei tratti. Come tratti marcatore, non hanno nemmeno metodi da implementare. Sono semplicemente utili per far rispettare gli invarianti legati alla concorrenza.
Implementare manualmente questi tratti comporta l’implementazione di codice Rust non sicuro. Parleremo dell’uso di codice Rust non sicuro nel Capitolo 19; per ora, le informazioni importanti sono che la creazione di nuovi tipi concorrenti non composti da parti Send e Sync richiede una riflessione attenta per mantenere le garanzie di sicurezza. “Il Rustonomicon” fornisce maggiori informazioni su queste garanzie e su come mantenerle. Sommario
Questo non è l’ultimo che vedrai della concorrenza in questo libro: il progetto nel Capitolo 20 utilizzerà i concetti di questo capitolo in una situazione più realistica rispetto agli esempi più piccoli discussi qui.
Come già accennato, poiché molto poco di come Rust gestisce la concorrenza fa parte del linguaggio, molte soluzioni di concorrenza sono implementate come crate. Queste si evolvono più rapidamente rispetto alla libreria standard, quindi assicurati di cercare online le crate più aggiornate da utilizzare in situazioni multithread.
La libreria standard di Rust fornisce canali per la trasmissione di messaggi e tipi di puntatori intelligenti, come Mutex<T> e Arc<T>, che sono sicuri da utilizzare in contesti concorrenti. Il sistema di tipi e il controllore dei mutui assicurano che il codice che utilizza queste soluzioni non finirà con le race condition o riferimenti non validi. Una volta che hai fatto compilare il tuo codice, puoi stare tranquillo 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 da temere: procedi e rendi i tuoi programmi concorrenti, senza paura!
Successivamente, parleremo dei modi idiomatici per modellare i problemi e strutturare le soluzioni man mano che i tuoi programmi Rust diventano più grandi. Inoltre, parleremo di come gli idiomi di Rust sono correlati a quelli che potresti conoscere dalla programmazione orientata agli oggetti.