原子操作也是线程同步重要的一部分。原子的意思是排他,即同一时刻,仅能有一个线程对数据进行操作。原子操作相对于mutex等其他锁操作,成本更低,一般是通过硬件支持的。但原子操作的使用范围也很有限,一般仅能用于基础数据类型的。如:int, bool, usize等基础类型。
下面是一个使用AtomicBool类型控制线程实例:
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
fn main() {
let flag = Arc::new(AtomicBool::new(false));
let cflag = flag.clone();
let hdl = thread::spawn(move || {
let mut counter = 0;
while !cflag.load(Ordering::SeqCst) {
thread::sleep(Duration::from_millis(100));
println!("counter: {}", {counter += 1; counter});
}
});
thread::sleep(Duration::from_secs(1));
flag.store(true, Ordering::SeqCst);
hdl.join().unwrap();
}
上面的例子简单展示了使用原子变量共享内存的方式,控制子线程退出。
谈到原子操作不得不谈到指令重排,正如上面例子中的Ordering::SeqCst。Rust的指令重排包括:
-
顺序一致性 (SeqCst):指令强顺序,即该指令会产生一个
内存屏障,在这个指令前的代码不可被重排到这个指令后;这个指令后的代码也不能重排到这个指令前。 -
获取 (Acquire): 即这个指令后的代码不可以被重排到这个指令前;这个指令前的代码可以被重排到这个指令后。
-
释放 (Release):即这个指令前的代码不可以被重排到这个指令后;这个指令后的代码可以被重排到这个指令前。
-
Relaxed: 弱指令顺序,即代码可以不受限制的被编译器进行重排。