Retour à l'accueil

Arc et Mutex pour l'état parallèle en Rust

L'article couvre l'utilisation d'Arc et de Mutex pour un accès sûr à l'état partagé dans les applications Rust multithreadées. Il inclut un exemple de compteur, une comparaison de pointeurs intelligents et des recommandations d'optimisation. Convient aux développeurs intermédiaires/seniors.

État parallèle en Rust : Arc + Mutex en pratique
Advertisement 728x90

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.

Google AdInline article slot

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é :

Google AdInline article slot
  • 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.

Google AdInline article slot

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> avec Mutex<T> ou RwLock<T> pour la sécurité des threads.
  • Évitez unwrap() sur lock() : gérez PoisonError.
  • 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

Advertisement 728x90

Lire ensuite