Rust
Onveilige richtlijnen
Zoeken…
Invoering
Leg uit waarom bepaalde dingen in Rust als unsafe
zijn gemarkeerd en waarom we dit ontsnappingsluik in bepaalde (zeldzame) situaties mogelijk moeten gebruiken.
Gegevensraces
Gegevensraces vinden plaats wanneer een stukje geheugen door een partij wordt bijgewerkt, terwijl een andere het tegelijkertijd probeert te lezen of bij te werken (zonder synchronisatie tussen de twee). Laten we eens kijken naar het klassieke voorbeeld van een gegevensrace met een gedeelde teller.
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());
}
De uitvoer zal bijna altijd minder zijn dan 10000
(10 threads x 1000) wanneer deze op een multi-coreprocessor wordt uitgevoerd.
In dit voorbeeld heeft een gegevensrace een logisch verkeerde maar nog steeds betekenisvolle waarde opgeleverd. Dit komt omdat er maar één woord bij de race betrokken was en dus een update het niet gedeeltelijk had kunnen veranderen. Maar dataraces kunnen in het algemeen corrupte waarden produceren die ongeldig zijn voor een type (type onveilig) wanneer het object dat wordt geracet meerdere woorden omvat, en / of waarden produceren die wijzen op ongeldige geheugenlocaties (geheugenonveilig) wanneer het om wijzers gaat.
Zorgvuldig gebruik van atomaire primitieven kan het echter mogelijk maken zeer efficiënte gegevensstructuren te bouwen die intern mogelijk een aantal van deze "onveilige" bewerkingen moeten uitvoeren om acties uit te voeren die niet statisch verifieerbaar zijn door het systeemtype van Rust, maar over het algemeen correct zijn (dwz om een veilige abstractie).