Rust
Небезопасные рекомендации
Поиск…
Вступление
Объясните, почему некоторые вещи отмечены unsafe
в Rust, и почему нам может понадобиться использовать этот люк в определенных (редких) ситуациях.
Гонки данных
Гонки данных происходят, когда часть памяти обновляется одной стороной, а другая пытается ее прочитать или обновить одновременно (без синхронизации между ними). Давайте рассмотрим классический пример гонки данных, используя общий счетчик.
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());
}
При работе на многоядерном процессоре выход почти всегда будет меньше 10000
(10 потоков × 1000).
В этом примере гонка данных породила логически неправильную, но все еще значимую ценность. Это связано с тем, что в гонке участвовало только одно слово, и, таким образом, обновление не могло частично его изменить. Но расы данных в целом могут приводить к поврежденным значениям, которые являются недопустимыми для типа (тип небезопасным), когда объект, который ведется, охватывает несколько слов и / или создает значения, указывающие на недопустимые ячейки памяти (небезопасные ячейки памяти), когда указатели задействованы.
Тем не менее, тщательное использование атомных примитивов может позволить построить очень эффективные структуры данных, которые могут внутренне нуждаться в выполнении некоторых из этих «опасных» операций для выполнения действий, которые не статически проверяются системой типа Rust, но правильны в целом (т.е. для создания безопасного абстракция).