W standardowej bibliotece Rust znajdują się podstawowe prymitywy synchronizacji do bezpiecznej pracy z wielowątkowością:
AtomicBool, AtomicUsize itd.) — operacje odczytu/zapisu bez blokad, na poziomie sprzętowym;Przykład:
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!("Wynik: {}", *counter.lock().unwrap()); }
Pytanie: Czy Rust gwarantuje, że użycie Mutex<T> całkowicie eliminuje deadlocki dzięki sprawdzeniu na etapie kompilacji?
Odpowiedź: Nie. Rust zapewnia bezpieczny dostęp do danych poprzez własność i borrow-checker, ale nie chroni przed deadlockami na poziomie języka. Martwe blokady powstają czysto logicznie w wyniku naruszenia porządku przechwytywania wielu Mutex lub ich rekursywnego przechwytywania. Przykład:
use std::sync::Mutex; let lock1 = Mutex::new(0); let lock2 = Mutex::new(0); // Wątek 1: lock1 -> lock2, Wątek 2: lock2 -> lock1 ⇒ deadlock
Historia
Historia
Mutex<Option<T>> na RwLock<T>, nie wzięto pod uwagę, że blokada zapisu może być dłuższa niż blokada odczytu. W okresie szczytowego obciążenia spowodowało to opóźnienia w przetwarzaniu do kilkudziesięciu sekund z powodu kolejek na zapis.Historia
Programiści próbowali oszczędzać na wątkach, "popychali" Arc<Mutex<_>> do setek wątków. Z powodu niuansów pracy planisty i ponownego wykorzystania mutex blokady pojawiały się zdumiewające wzajemne oczekiwania — wydajność spadła pięciokrotnie!