Zurück zur Startseite

Arc und Mutex für parallelen Zustand in Rust

Der Artikel behandelt die Verwendung von Arc und Mutex für sicheren Zugriff auf gemeinsamen Zustand in mehrthreadigen Rust-Anwendungen. Er enthält ein Zähler-Beispiel, Vergleich von Smart Pointers und Optimierungsempfehlungen. Geeignet für mittlere/senior Entwickler.

Paralleler Zustand in Rust: Arc + Mutex in der Praxis
Advertisement 728x90

Sicherer Zugriff auf gemeinsamen Zustand in Multithread-Rust-Anwendungen

Ungeschützte Speicherfreigabe zwischen Threads führt zu Wettlaufsituationen, bei denen gleichzeitige Lese- und Schreibvorgänge Daten beschädigen. Selbst mit Synchronisierung können Deadlocks auftreten, die die Ausführung verlangsamen. Das Debuggen solcher Codes ist herausfordernd, obwohl Rust viele Fehler zur Kompilierzeit verhindert.

In hochbelasteten Systemen wie Zählern oder Caches ist gemeinsamer Zugriff gerechtfertigt, wenn geeignete Synchronisationsprimitive verwendet werden.

Arc und Mutex für threadsichere Datenteilung

Für sichere Datenübertragung zwischen Threads kombiniere Arc<T> und Mutex<T>. Arc<T> bietet atomare Referenzzählung mit Send- und Sync-Unterstützung, was Mehrfacheigentum in einer Multithread-Umgebung ermöglicht. Mutex<T> garantiert gegenseitigen Ausschluss: Nur ein Thread greift gleichzeitig auf Daten zu.

Google AdInline article slot

Arc wird für jeden Thread mit Arc::clone geklont, und der Zugriff erfolgt über lock(), was einen MutexGuard zurückgibt. Die lock()-Methode kann bei einem vergifteten Mutex paniken, daher sollten Fehler in der Produktion behandelt werden, anstatt unwrap zu verwenden.

Beispiel für eine Zählerimplementierung

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);
}

Dieser Code demonstriert das Inkrementieren in einem Thread und Lesen in einem anderen. Der Endwert ist immer 10, ohne Datenrennen.

Vergleich wichtiger Smart Pointer

Rust bietet mehrere Smart Pointer für die Speicherverwaltung. Die Wahl hängt von Threadsicherheit und Eigentumsanforderungen ab:

Google AdInline article slot
  • Box<T>: Einfaches Eigentum auf dem Heap, geeignet für Typen unbekannter Größe, nicht Send + Sync.
  • Rc<T>: Mehrfacheigentum innerhalb eines einzelnen Threads, nicht threadsicher.
  • Arc<T>: Mehrfacheigentum über Threads hinweg, Send + Sync, mit Overhead durch atomare Operationen.
  • RefCell<T>: Interne Veränderbarkeit mit Laufzeit-Borrowing-Prüfungen, single-threaded.
  • Cell<T>: Leichte Veränderbarkeit für Copy-Typen, keine Paniken.
  • Mutex<T>: Sperren für exklusiven Zugriff, Risiko von Deadlocks.
  • RwLock<T>: Optimierung für leselastige Szenarien.
  • Weak<T>: Schwache Referenzen, um Zyklen in Rc/Arc zu brechen.
  • ManuallyDrop<T>: Manuelle Destruktorsteuerung, unsicher.

Arc<T> ist in std einzigartig für threadsicheres Mehrfacheigentum.

Mutex-Alternativen zur Optimierung

In Szenarien mit häufigen Lesevorgängen ziehe RwLock<T> in Betracht: Mehrere Leser können parallel arbeiten, Schreiber sind exklusiv. Die Leistung ist höher, wenn das Leser/Schreiber-Verhältnis > 1 ist.

Für sperrenfreien Zugriff verwende Atomics (std::sync::atomic), wenn es sich um primitive Datentypen wie AtomicI64 handelt. Sie vermeiden Sperren vollständig.

Google AdInline article slot

Wichtige Erkenntnisse

  • Gemeinsamer Zugriff ist nur in hochbelasteten Systemen mit gemeinsamen Daten gerechtfertigt; andernfalls bevorzuge Eigentumsübertragung.
  • Kombiniere immer Arc<T> mit Mutex<T> oder RwLock<T> für Threadsicherheit.
  • Vermeide unwrap() bei lock(): Behandle PoisonError.
  • Arc<T> hat Overhead durch atomare Operationen – messe in Benchmarks.
  • Für einfache Fälle verwende Kanäle (mpsc) anstelle von gemeinsamem Zustand.

— Editorial Team

Advertisement 728x90

Weiterlesen