Accès sécurisé à l'état partagé dans les applications Rust multithreadées
Le partage de mémoire non protégé entre threads conduit à des conditions de concurrence, où des lectures et écritures simultanées corrompent les données. Même avec synchronisation, des interblocages peuvent survenir, ralentissant l'exécution. Déboguer ce type de code est complexe, bien que Rust prévienne de nombreuses erreurs à la compilation.
Dans les systèmes à forte charge comme les compteurs ou les caches, l'accès partagé est justifié lorsqu'on utilise des primitives de synchronisation appropriées.
Arc et Mutex pour le partage de données thread-safe
Pour transférer des données en toute sécurité entre threads, combinez Arc<T> et Mutex<T>. Arc<T> fournit un comptage de références atomique avec support Send et Sync, permettant une propriété multiple dans un environnement multithreadé. Mutex<T> garantit l'exclusion mutuelle : un seul thread accède aux données à la fois.
Arc est cloné pour chaque thread avec Arc::clone, et l'accès est obtenu via lock(), qui retourne un MutexGuard. La méthode lock() peut paniquer avec un mutex empoisonné, donc en production, gérez les erreurs au lieu d'utiliser unwrap.
Exemple d'implémentation d'un compteur
use std::thread;
use std::time::Duration;
use std::sync::{Arc, Mutex};
struct Counter {
value: i64
}
fn main() {
let counter = Arc::new(Mutex::new(Counter {value: 0}));
let increment = Arc::clone(&counter);
let handler1 = thread::spawn(move || {
for _ in 0..10 {
let mut guard = increment.lock().unwrap();
guard.value += 1;
println!("inc: {}", guard.value);
thread::sleep(Duration::from_millis(100));
}
});
let reader = Arc::clone(&counter);
let handler2 = thread::spawn(move || {
for _ in 0..15 {
let guard = reader.lock().unwrap();
println!("read: {}", guard.value);
thread::sleep(Duration::from_millis(180));
}
});
handler1.join().unwrap();
handler2.join().unwrap();
let final_guard = counter.lock().unwrap();
println!("final: {}", final_guard.value);
}
Ce code démontre l'incrémentation dans un thread et la lecture dans un autre. La valeur finale est toujours 10, sans course aux données.
Comparaison des principaux pointeurs intelligents
Rust propose plusieurs pointeurs intelligents pour la gestion mémoire. Le choix dépend des exigences de sécurité des threads et de propriété :
- Box<T> : Propriété unique sur le tas, adapté aux types de taille inconnue, pas
Send + Sync. - Rc<T> : Propriété multiple dans un seul thread, pas thread-safe.
- Arc<T> : Propriété multiple entre threads,
Send + Sync, avec surcoût des opérations atomiques. - RefCell<T> : Mutabilité intérieure avec vérifications d'emprunt à l'exécution, monothreadé.
- Cell<T> : Mutabilité légère pour types
Copy, pas de paniques. - Mutex<T> : Verrouillage pour accès exclusif, risque d'interblocages.
- RwLock<T> : Optimisation pour scénarios de lecture intensive.
- Weak<T> : Références faibles pour rompre les cycles dans
Rc/Arc. - ManuallyDrop<T> : Contrôle manuel du destructeur, unsafe.
Arc<T> est unique dans la bibliothèque standard pour la propriété multiple thread-safe.
Alternatives à Mutex pour l'optimisation
Dans les scénarios avec lectures fréquentes, considérez RwLock<T> : plusieurs lecteurs peuvent procéder en parallèle, les écrivains sont exclusifs. Les performances sont supérieures quand le ratio lecteur/écrivain > 1.
Pour un accès sans verrou, utilisez les atomiques (std::sync::atomic) si les données sont primitives comme AtomicI64. Ils évitent complètement les verrous.
Points clés à retenir
- L'accès partagé n'est justifié que dans les systèmes à forte charge avec données partagées ; sinon, préférez le transfert de propriété.
- Combinez toujours
Arc<T>avecMutex<T>ouRwLock<T>pour la sécurité des threads. - Évitez
unwrap()surlock(): gérezPoisonError. Arc<T>entraîne un surcoût des opérations atomiques — mesurez dans les benchmarks.- Pour les cas simples, utilisez les canaux (
mpsc) au lieu de l'état partagé.
— Editorial Team
Aucun commentaire pour le moment.