Bezpečný přístup ke sdílenému stavu v vícevláknových aplikacích v Rustu
Sdílený přístup k paměti mezi vlákny bez ochrany vede k datovým závodům, kdy souběžné čtení a zápis poškozují data. I se synchronizací vznikají zámky, které zpomalují provádění. Ladění takového kódu je složité, ačkoli Rust mnoho chyb odhalí už při kompilaci.
Ve vysoce zatížených systémech, jako jsou čítače nebo cache, je sdílený přístup oprávněný, pokud se použijí správné synchronizační primitivy.
Arc a Mutex pro vláknově bezpečnou výměnu dat
Pro bezpečný přenos dat mezi vlákny se kombinují Arc<T> a Mutex<T>. Arc<T> zajišťuje atomické počítání referencí s podporou Send a Sync, což umožňuje vícenásobné vlastnictví v prostředí s více vlákny. Mutex<T> garantuje vzájemné vyloučení: pouze jedno vlákno získá přístup v daném okamžiku.
Arc se klonuje pro každé vlákno pomocí Arc::clone a přístup se otevírá přes lock(), který vrací MutexGuard. Metoda lock() může skončit panikou při otráveném mutexu, proto se v produkci používá zpracování chyb místo unwrap.
Příklad implementace čítače
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);
}
Tento kód demonstruje inkrementaci v jednom vlákně a čtení v druhém. Konečná hodnota je vždy 10, bez datových závodů.
Srovnání klíčových chytrých ukazatelů
Rust nabízí několik chytrých ukazatelů pro správu paměti. Výběr závisí na požadavcích na vláknovou bezpečnost a vlastnictví:
- Box<T>: Jednoduché vlastnictví na haldě, vhodné pro typy neznámé velikosti, není
Send + Sync. - Rc<T>: Vícenásobné vlastnictví v jednom vlákně, není vláknově bezpečný.
- Arc<T>: Vícenásobné vlastnictví mezi vlákny,
Send + Sync, režie na atomické operace. - RefCell<T>: Vnitřní proměnlivost s kontrolou půjčování za běhu, jedno vlákno.
- Cell<T>: Lehká proměnlivost pro typy
Copy, bez panik. - Mutex<T>: Zámek pro exkluzivní přístup, riziko uváznutí.
- RwLock<T>: Optimalizace pro scénáře s převahou čtení.
- Weak<T>: Slabé reference pro přerušení cyklů v
Rc/Arc. - ManuallyDrop<T>: Ruční správa destruktoru, není bezpečné.
Arc<T> je v std jedinečný pro vláknově bezpečné vícenásobné vlastnictví.
Alternativy k Mutex pro optimalizaci
Ve scénářích s častým čtením zvažte RwLock<T>: více čtenářů běží paralelně, zapisovatelé exkluzivně. Výkon je vyšší při poměru čtenáři/zapisovatelé > 1.
Pro přístup bez zámků použijte atomické typy (std::sync::atomic), pokud jsou data primitiva jako AtomicI64. Tím se zámkům úplně vyhnete.
Co je důležité
- Sdílený přístup je oprávněný pouze ve vysoce zatížených systémech se sdílenými daty, jinak preferujte předávání vlastnictví.
- Vždy kombinujte
Arc<T>sMutex<T>neboRwLock<T>pro vláknovou bezpečnost. - Vyhněte se
unwrap()nalock(): zpracujtePoisonError. Arc<T>nese režii atomických operací – měřte v benchmarkech.- Pro jednoduché případy použijte kanály (
mpsc) místo sdíleného stavu.
— Editorial Team
Zatím žádné komentáře.