Rust
Niebezpieczne wytyczne
Szukaj…
Wprowadzenie
Wyjaśnij, dlaczego niektóre rzeczy są oznaczone jako unsafe
w Rust i dlaczego może być konieczne użycie tego włazu ratunkowego w niektórych (rzadkich) sytuacjach.
Wyścigi danych
Wyścigi danych mają miejsce, gdy część pamięci jest aktualizowana przez jedną ze stron, podczas gdy druga próbuje ją odczytać lub zaktualizować jednocześnie (bez synchronizacji między nimi). Spójrzmy na klasyczny przykład wyścigu danych z wykorzystaniem wspólnego licznika.
use std::cell::UnsafeCell;
use std::sync::Arc;
use std::thread;
// `UnsafeCell` is a zero-cost wrapper which informs the compiler that "what it
// contains might be shared mutably." This is used only for static analysis, and
// gets optimized away in release builds.
struct RacyUsize(UnsafeCell<usize>);
// Since UnsafeCell is not thread-safe, the compiler will not auto-impl Sync for
// any type containig it. And manually impl-ing Sync is "unsafe".
unsafe impl Sync for RacyUsize {}
impl RacyUsize {
fn new(v: usize) -> RacyUsize {
RacyUsize(UnsafeCell::new(v))
}
fn get(&self) -> usize {
// UnsafeCell::get() returns a raw pointer to the value it contains
// Dereferencing a raw pointer is also "unsafe"
unsafe { *self.0.get() }
}
fn set(&self, v: usize) { // note: `&self` and not `&mut self`
unsafe { *self.0.get() = v }
}
}
fn main() {
let racy_num = Arc::new(RacyUsize::new(0));
let mut handlers = vec![];
for _ in 0..10 {
let racy_num = racy_num.clone();
handlers.push(thread::spawn(move || {
for i in 0..1000 {
if i % 200 == 0 {
// give up the time slice to scheduler
thread::yield_now();
// this is needed to interleave the threads so as to observe
// data race, otherwise the threads will most likely be
// scheduled one after another.
}
// increment by one
racy_num.set(racy_num.get() + 1);
}
}));
}
for th in handlers {
th.join().unwrap();
}
println!("{}", racy_num.get());
}
Wyjście prawie zawsze będzie mniejsze niż 10000
(10 wątków × 1000), gdy będzie działać na procesorze wielordzeniowym.
W tym przykładzie wyścig danych wygenerował logicznie niepoprawną, ale wciąż znaczącą wartość. Jest tak, ponieważ w wyścigu uczestniczyło tylko jedno słowo, dlatego aktualizacja nie mogła go częściowo zmienić. Jednak wyścigi danych mogą generować uszkodzone wartości, które są niepoprawne dla typu (niebezpieczne), gdy obiekt, który jest śledzony, obejmuje wiele słów i / lub generują wartości wskazujące na nieprawidłowe lokalizacje pamięci (niebezpieczne), gdy w grę wchodzą wskaźniki.
Jednak ostrożne stosowanie prymitywów atomowych może umożliwić budowę bardzo wydajnych struktur danych, które mogą wewnętrznie wymagać wykonania niektórych z tych „niebezpiecznych” operacji w celu wykonania działań, które nie są statycznie weryfikowalne przez system typu Rust, ale ogólnie poprawne (tj. Zbudować bezpieczny abstrakcja).