ProgrammationDéveloppeur de bibliothèques systèmes / Développeur Backend

Comment fonctionne la « sécurité multithread » (Send et Sync) en Rust et comment peut-on les implémenter/restrict pour des types personnalisés ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

En Rust, la sécurité lors de l'utilisation des threads est assurée par deux traits automatiques : Send et Sync.

  • Send permet de transférer un type entre des threads (en transférant la propriété).
  • Sync garantit qu'un type peut être utilisé en toute sécurité depuis plusieurs threads simultanément (grâce à des références).

La plupart des types standards en Rust implémentent ces traits par défaut. Par exemple, Arc<T>, Mutex<T>Send et Sync (si T respecte également ces traits).

Vous pouvez explicitement interdire ou implémenter ces traits pour vos types. Par exemple, si vous avez un champ interne non sécurisé (comme un pointeur brut ou des ressources externes), vous devez faire en sorte que les types soient !Send ou !Sync :

use std::marker::PhantomData; use std::rc::Rc; struct MyType { not_thread_safe: Rc<u32>, _marker: PhantomData<*const ()>, } // Rc<u32> n'implémente pas Send/Sync, donc MyType n'implémentera également pas ces traits.

Si vous implémentez un wrapper de bas niveau et gérez manuellement la sécurité des threads, vous pouvez implémenter Send/Sync manuellement (unsafe) :

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

C'est la responsabilité du programmeur de garantir la sécurité des threads.

Question piège.

Que se passera-t-il si l'on passe Rc<T> à plusieurs threads via Arc<Rc<T>> ?

On pense souvent qu'Arc protège tout. Mais Rc<T> n'implémente pas Send/Sync, même s'il est enveloppé dans un Arc ! Voici comment :

use std::rc::Rc; use std::sync::Arc; fn main() { let data = Arc::new(Rc::new(5)); // std::thread::spawn(move || { // println!("{:?}", data); // }); // Le compilateur ne permettra pas de le faire ! }

Arc ne compense pas l'absence de Send/Sync des membres imbriqués.

Exemples d'erreurs réelles dues à une méconnaissance des subtilités du sujet.


Histoire

Dans un projet, il y avait une tentative d'utiliser Arc<Rc<T>> pour partager des données entre threads et diviser la propriété de manière non bloquante. Le programme s'est arrêté pendant l'exécution avec des comportements imprévisibles ; il s'est avéré que Rc n'est pas sécurisé pour les threads, et un manque de connaissance sur les traits Send/Sync en était la cause initiale.


Histoire

Dans une boucle d'événements faite maison, le type State stockait un pointeur brut vers des données. Le type State a été marqué unsafe impl Send, mais il a oublié de mettre en place la synchronisation. Cela a conduit à une classique data race, détectée après la sortie.


Histoire

Un développeur a implémenté un wrapper de newtype autour de Mutex, mais a erroneusement déclaré qu'il était !Sync, n'impliquant pas Sync manuellement. Cela empêchait d'utiliser le type dans un contexte multithread (par exemple, à l'intérieur de Arc<Mutex<T>>), car le compilateur exigeait Sync. Corrigé en implémentant unsafe impl Sync et en analysant la sécurité.