数据竞争
当一方存储器被一方更新而另一方尝试同时读取或更新存储器时(两者之间没有同步),就会发生数据争用。让我们看一下使用共享计数器的数据竞争的经典示例。
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 类型系统无法静态验证的操作,但总体上是正确的(即构建一个安全的抽象)。