ProgrammationIngénieur en calcul parallèle (Rust)

Comment fonctionne la multiprogrammation sécurisée en Rust : que signifient les marker-traits Send et Sync, comment contrôlent-ils le passage et le partage des données entre les threads, et comment un développeur doit-il implémenter (ou interdire) ces traits pour ses propres types ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Les programmeurs ont depuis longtemps été confrontés à la question de la sécurité dans les environnements multithreads, rencontrant fréquemment des problèmes de concurrence, de données incohérentes et de fuites de mémoire. Rust a mis en œuvre une approche unique avec les marker-traits Send et Sync pour minimiser ces problèmes dès la phase de compilation.

Problème : l'absence de contrôle d'accès aux données partagées entre les threads, conduisant à des erreurs difficiles à détecter. Dans de nombreux langages, la responsabilité incombe entièrement au programmeur, tandis qu'en Rust, le compilateur vérifie ce qui peut être transféré/partagé entre les threads.

Solution : le trait Send garantit la possibilité de transférer un objet d'un thread à un autre en toute sécurité. Sync — la possibilité d'accéder simultanément à une référence d'objet à partir de différents threads. Presque tous les types standard en Rust implémentent automatiquement ces traits, tandis que les types personnalisés peuvent les implémenter manuellement ou les interdire via impl !Send ou impl !Sync pour des cas spécifiques.

Exemple de code :

use std::sync::{Arc, Mutex}; use std::thread; let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } // counter sera toujours égal à 10 sans course !

Caractéristiques clés :

  • Send signifie le transfert de la possession d'un objet entre les threads.
  • Sync signifie un partage sécurisé d'un objet via des références.
  • Implémentation/interdiction des traits pour ses propres types permet de contrôler le comportement au moment de la compilation.

Questions pièges.

Un type avec des pointeurs bruts peut-il être Send ou Sync ?

Non, si un type contient un pointeur brut ou des ressources sans garanties de sécurité multithread, il n'implémente pas ces traits, ou le développeur doit les implémenter manuellement en prenant l'entière responsabilité (souvent avec unsafe impl Send/Sync).

Rc<T> et RefCell<T> sont-ils Send ou Sync ?

Non, Rc<T> et RefCell<T> ne sont pas sûrs pour une utilisation multithread (ni Send, ni Sync). Pour les scénarios multithreads, Arc<T> et Mutex/RwLock sont utilisés.

Que se passe-t-il si une variable statique contient un type sans Sync implémenté ?

Rust ne permettra pas à une telle variable statique d'exister : elle doit être Sync, sinon le compilateur renverra une erreur.

Erreurs typiques et anti-patterns

  • Utiliser Rc<T> au lieu de Arc<T> lors d'un accès partagé depuis plusieurs threads.
  • Développer des structures avec des pointeurs bruts internes et faire confiance automatiquement au trait Send.
  • Violer des invariants en utilisant unsafe impl Send/Sync sans contrôle strict.

Exemple de la vie réelle

Cas négatif

Un jeune développeur place un objet Rc dans thread::spawn — le code ne compile que si Rc n'est pas passé entre les threads. En essayant de retirer Rc de thread::spawn, une erreur de compilation se produit, car Rc n'implémente pas Send et n'est pas protégé contre les courses.

Avantages :

  • Le compilateur empêche immédiatement l'erreur de course sur les données.

Inconvénients :

  • Si l'on ne connaît pas la différence entre Rc et Arc, il est difficile de comprendre l'erreur.

Cas positif

Utilise Arc+Mutex pour un compteur multithread, tous les threads travaillent avec les mêmes données via une interface sécurisée pour les threads. Pas de courses, le code est sûr et robuste.

Avantages :

  • Pas de courses, sécurité de la mémoire, utilisation des marker-traits pour contrôler le comportement.

Inconvénients :

  • Mutex et Arc ont un overhead, nécessite la connaissance des primitives sécurisées pour les threads.