Rust
Directives dangereuses
Recherche…
Introduction
Expliquez pourquoi certaines choses sont marquées comme unsafe
dans Rust et pourquoi nous pourrions avoir besoin d'utiliser cette trappe d'évacuation dans certaines situations (rares).
Courses de données
Les courses de données se produisent quand une partie de la mémoire est mise à jour par une partie, tandis qu'une autre tente de la lire ou de la mettre à jour simultanément (sans synchronisation entre les deux). Regardons l'exemple classique d'une course de données en utilisant un compteur partagé.
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());
}
La sortie sera presque toujours inférieure à 10000
(10 threads × 1000) lorsqu'elle est exécutée sur un processeur multicœur.
Dans cet exemple, une course de données a produit une valeur logiquement incorrecte mais toujours significative. C'est parce que seul un mot a été impliqué dans la course et donc une mise à jour n'a pas pu la modifier partiellement. Mais les courses de données en général peuvent produire des valeurs de corruption non valides pour un type (type non sécurisé) lorsque l'objet en cours de course couvre plusieurs mots et / ou produire des valeurs pointant vers des emplacements mémoire invalides (mémoire non sécurisée).
Toutefois, une utilisation prudente des primitives atomiques peut permettre la construction de structures de données très efficaces pouvant nécessiter certaines de ces opérations "peu sûres" pour effectuer des actions qui ne sont pas vérifiables statiquement par le système de type Rust, abstraction).