ProgrammazioneSviluppatore di librerie di sistema / Sviluppatore Backend

Come funziona la «sicurezza della multithreading» (Send e Sync) in Rust e come possono essere implementati/o limitati per i tipi utente?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

In Rust, la sicurezza nella gestione dei thread è garantita da due trait automatici: Send e Sync.

  • Send consente di passare un tipo tra thread (attraverso il passaggio di proprietà).
  • Sync garantisce che un tipo possa essere utilizzato in sicurezza da più thread contemporaneamente (attraverso riferimenti).

La maggior parte dei tipi standard in Rust implementa questi trait per impostazione predefinita. Ad esempio, Arc<T>, Mutex<T>Send e Sync (se T soddisfa anch'esso questi trait).

È possibile vietare esplicitamente o implementare questi trait per i propri tipi. Ad esempio, se si ha un campo interno non sicuro (come un puntatore grezzo o risorse esterne), è opportuno rendere i tipi !Send o !Sync:

use std::marker::PhantomData; use std::rc::Rc; struct MyType { not_thread_safe: Rc<u32>, _marker: PhantomData<*const ()>, } // Rc<u32> non implementa Send/Sync, quindi MyType non implementerà tali trait.

Se si implementa un wrapper a basso livello e si gestisce manualmente la sicurezza dei thread, è possibile implementare Send/Sync manualmente (unsafe):

unsafe impl Send for MyType {} unsafe impl Sync for MyType {}

È responsabilità del programmatore fornire garanzie di sicurezza per i thread.

Domanda trabocchetto.

Cosa succede se si porta Rc<T> in diversi thread tramite Arc<Rc<T>>?

Si pensa spesso che Arc protegga tutto. Ma Rc<T> non implementa Send/Sync, anche se avvolto in Arc! Ecco come:

use std::rc::Rc; use std::sync::Arc; fn main() { let data = Arc::new(Rc::new(5)); // std::thread::spawn(move || { // println!("{:?}", data); // }); // Il compilatore non permetterà di fare questo! }

Arc non compensa l'assenza di Send/Sync nei membri interni.

Esempi di errori reali dovuti alla mancanza di conoscenza delle complessità del tema.


Storia

Nel progetto è stato tentato di usare Arc<Rc<T>> per condividere i dati tra i thread e dividere la proprietà in modo non bloccante. Il programma è crollato durante l'esecuzione con comportamenti imprevedibili; si è scoperto che Rc non è thread-safe, e questa mancanza di conoscenza riguardo ai trait Send/Sync è stata inizialmente alla base del problema.


Storia

In un ciclo di eventi auto-creato, il tipo State memorizzava un puntatore grezzo ai dati. Il tipo State è stato contrassegnato come unsafe impl Send, ma si è dimenticato di aggiungere la sincronizzazione. Di conseguenza, è emersa una classica data race, scoperta già dopo il rilascio.


Storia

Uno sviluppatore ha implementato un wrapper newtype su Mutex, ma per errore lo ha reso !Sync, senza implementare Sync manualmente. Ciò ha impedito l'uso del tipo in un contesto multithread (ad esempio, all'interno di Arc<Mutex<T>>), poiché il compilatore richiedeva Sync. Risolto implementando unsafe impl Sync e analizzando la sicurezza.