Appearance
29.4 Atomic
Rust 标准库还为我们提供了一系列的“原子操作”数据类型,它们在std::sync::atomic
模块里面。它们都是符合 Sync 的,可以在多线程之间共享。比如,我们有 AtomicIsize 类型,顾名思义,它对应的是 isize 类型的“线程安全”版本。我们知道,普通的整数读取再写入,这种操作是非原子的。而原子整数的特点是,可以把“读取”“计算”“再写入”这样的操作编译为特殊的 CPU 指令,保证这个过程是原子操作。
我们来看一个示例:
rust
use std::sync::Arc;
use std::sync::atomic::{AtomicIsize, Ordering};
use std::thread;
const COUNT: u32 = 1000000;
fn main() {
// Atomic 系列类型同样提供了线程安全版本的内部可变性
let global = Arc::new(AtomicIsize::new(0));
let clone1 = global.clone();
let thread1 = thread::spawn(move|| {
for _ in 0..COUNT {
clone1.fetch_add(1, Ordering::SeqCst);
}
});
let clone2 = global.clone();
let thread2 = thread::spawn(move|| {
for _ in 0..COUNT {
clone2.fetch_sub(1, Ordering::SeqCst);
}
});
thread1.join().ok();
thread2.join().ok();
println!("final value: {:?}", global);
}
这个示例我们很熟悉:两个线程修改同一个整数,一个线程对它进行多次加 1,另外一个线程对它多次减 1。这次我们发现,使用了 Atomic 类型后,我们可以保证最后的执行结果一定会回到 0。
我们还可以把这段代码改动一下:
rust
use std::sync::Arc;
use std::sync::atomic::{AtomicIsize, Ordering};
use std::thread;
const COUNT: u32 = 1000000;
fn main() {
let global = Arc::new(AtomicIsize::new(0));
let clone1 = global.clone();
let thread1 = thread::spawn(move|| {
for _ in 0..COUNT {
let mut value = clone1.load(Ordering::SeqCst);
value += 1;
clone1.store(value, Ordering::SeqCst);
}
});
let clone2 = global.clone();
let thread2 = thread::spawn(move|| {
for _ in 0..COUNT {
let mut value = clone2.load(Ordering::SeqCst);
value -= 1;
clone2.store(value, Ordering::SeqCst);
}
});
thread1.join().ok();
thread2.join().ok();
println!("final value: {:?}", global);
}
与上一个版本相比,这段代码的区别在于:我们没有使用原子类型自己提供的fetch_add
fetch_sub
方法,而是使用了 load 把里面的值读取出来,然后执行加/减,操作完成后,再用 store 存储回去。编译程序我们看到,是可以编译通过的。再执行,出现了问题:这次的执行结果就不是保证为0
了。
大家应该很容易看明白问题在哪里。原来的那种写法,“读取/计算/写入”是一个完整的“原子操作”,中间不可被打断,它是一个“事务”(transaction)。而后面的写法把“读取”作为了一个“原子操作”,“写入”又作为了一个“原子操作”,把一个 transaction 分成了两段来执行。上面的那个程序,其逻辑类似于“lock=>读数据=>加/减运算=>写数据=>unlock”。下面的程序,其逻辑类似于“lock=>读数据=>unlock=>加/减运算=>lock=>写数据=>unlock”。虽然每次读写共享变量都保证了唯一性,但逻辑还是错的。
所以,编译器只能防止基本的数据竞争问题。如果程序里面有逻辑错误,工具是没有办法帮我们发现的。上面这个示例中并不存在数据竞争问题,是完全的“业务逻辑 bug”。