Z problemem bezpiecznego działania w środowiskach wielowątkowych programiści zmagają się od dawna, niestrudzenie borykając się z problemami wyścigów, niespójnych danych i wycieków pamięci. W Rust wprowadzono unikalne podejście z marker-traitami Send i Sync, aby zminimalizować te problemy już na etapie kompilacji.
Problem — brak kontroli dostępu do współdzielonych danych między wątkami, prowadzący do trudnych do zdiagnozowania błędów. W wielu językach odpowiedzialność spoczywa w pełni na programiście, w Rust kompilator sam sprawdza, co można przekazywać/współdzielić między wątkami.
Rozwiązanie: trait Send zapewnia możliwość bezpiecznego przekazywania obiektu z jednego wątku do drugiego. Sync — możliwość wspólnego dostępu do referencji do obiektu z różnych wątków. Prawie wszystkie typy standardowe w Rust automatycznie implementują te traity, a typy niestandardowe mogą je zaimplementować ręcznie lub zabraniać przez impl !Send lub impl !Sync w specyficznych przypadkach.
Przykład kodu:
use std::sync::{Arc, Mutex}; use std::thread; 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(); } // counter zawsze będzie równy 10 bez wyścigów!
Najważniejsze cechy:
Czy typ z niebezpiecznymi wskaźnikami może być Send lub Sync?
Nie, jeśli typ zawiera raw pointer lub zasoby bez gwarancji bezpieczeństwa wątków, nie implementuje tych traitów, lub programista musi je zaimplementować ręcznie z pełną odpowiedzialnością (zwykle z unsafe impl Send/Sync).
Czy Rc<T> i RefCell<T> są Send lub Sync?
Nie, Rc<T> i RefCell<T> są niebezpieczne do użytku wielowątkowego (ani Send, ani Sync). Do scenariuszy wielowątkowych używają Arc<T> oraz Mutex/RwLock.
Co się stanie, jeśli zmienna statyczna zawiera typ bez zaimplementowanego Sync?
Rust nie pozwoli, aby taka zmienna statyczna istniała: musi być Sync, w przeciwnym razie kompilator zgłosi błąd.
Młody programista umieszcza obiekt Rc w thread::spawn — kod kompiluje się tylko wtedy, gdy Rc nie jest przekazywane między wątkami. Przy próbie wyciągnięcia Rc z thread::spawn występuje błąd kompilacji, ponieważ Rc nie implementuje Send i nie jest chronione przed wyścigami.
Zalety:
Wady:
Używane jest Arc+Mutex do wielowątkowego licznika, wszystkie wątki działają z tymi samymi danymi za pomocą interfejsu bezpiecznego dla wątków. Brak wyścigów, kod jest bezpieczny i odporny.
Zalety:
Wady: