Historique de la question :
Le travail avec la multithreading est une source d'erreurs dans la plupart des langages de programmation : les races de données, la concurrence pour les ressources, des bugs non évidents. En étudiant l'expérience de C++ et Java, les concepteurs de Rust ont décidé d'intégrer des mécanismes de sécurité des threads directement dans le système de types, afin que la plupart des erreurs soient détectées lors de la compilation.
Problème :
Dans les langages classiques, il est souvent nécessaire de compter sur la discipline du programmeur et sur des outils externes : les risques de transfert de propriété des données, la mémoire mutable partagée et l'absence de contrôle sur l'accès simultané peuvent entraîner des pannes critiques. Il était nécessaire d'assurer un système garantissant l'absence de race mémoire au moment de la compilation.
Solution :
En Rust, la synchronisation et le transfert de données entre threads utilisent des types spéciaux de la bibliothèque standard — par exemple, Arc, Mutex et des canaux. Les marqueurs de traits Send et Sync jouent un rôle clé, qui sont automatiquement vérifiés par le compilateur. Un type est considéré comme thread-safe si :
Sync)SendExemple de code :
use std::sync::{Arc, Mutex}; use std::thread; fn main() { 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(); } println!("Résultat : {}", *counter.lock().unwrap()); }
Caractéristiques clés :
Mutex, RwLock, les canaux — sont thread-safe par contratArc, Mutex), et non par des pointeursPourquoi ne peut-on pas utiliser Rc<T> pour transmettre des données entre les threads ?
Rc<T> n'implémente pas le trait Send et n'est pas thread-safe — l'implémentation interne est basée sur un compteur de références non bloquant, ce qui conduit à une race de données lors de l'accès depuis plusieurs threads. Pour les threads, utilisez Arc<T>.
Peut-on implémenter manuellement Send ou Sync pour son propre type afin de contourner les restrictions du compilateur ?
C'est possible, mais extrêmement dangereux ! Si vous enfreignez les invariants (par exemple, partagez un pointeur brut), vous obtiendrez une race de données. Réservez l'implémentation manuelle uniquement aux spécialistes qui sont totalement sûrs de la sécurité des threads du type.
Quand un Mutex peut-il entraîner un deadlock en Rust, et comment l'éviter ?
Un deadlock est possible si l'ordre de capture de plusieurs mutex n'est pas stable ou si le verrouillage est imbriqué récursivement dans un même thread (Mutex n'est pas réentrant !).
use std::sync::Mutex; let a = Mutex::new(0); let _g1 = a.lock().unwrap(); let _g2 = a.lock().unwrap(); // panic : deadlock !
Le développeur a utilisé Rc<RefCell<T>> pour transmettre l'état entre les threads dans un serveur web. Les tests locaux fonctionnaient, mais en production, des conditions de race sont apparues : parfois les variables "perdaient" des états, parfois le serveur plantait.
Avantages :
Inconvénients :
Utilisation de Arc<Mutex<T>> lors du transfert d'état, respect strict de Send/Sync, répartition du travail entre les threads via des canaux, pas de mutabilité des données partagées dans les threads.
Avantages :
Inconvénients :