面试题答案
一键面试Rust原子操作中内存顺序的含义和作用
-
SeqCst(顺序一致性)
- 含义:SeqCst 代表顺序一致性内存顺序。它是最严格的内存顺序。所有线程对原子操作都有一个全局的一致顺序。这意味着不仅所有线程对特定原子变量的操作顺序一致,而且所有原子操作之间也有一个全局顺序。
- 作用:用于需要严格顺序保证的场景,例如实现锁机制时,确保所有线程以相同顺序观察到共享资源的变化,避免复杂的竞态条件。
-
Acquire
- 含义:Acquire 内存顺序保证在一个线程中,对原子变量的读操作(带有 Acquire 顺序)之前的所有读/写操作,在该线程中不会被重排到读操作之后。
- 作用:常用于读取共享资源时,确保在读取之前,所有对该共享资源的写操作都已完成,从而获得最新的数据。
-
Release
- 含义:Release 内存顺序保证在一个线程中,对原子变量的写操作(带有 Release 顺序)之后的所有读/写操作,在该线程中不会被重排到写操作之前。
- 作用:常用于写入共享资源时,确保在写入之后,所有对该共享资源的后续操作都能被其他线程观察到,因为写入操作的效果已“释放”到内存中。
多线程程序中避免数据竞争和保证正确性的方法及示例
下面是一个使用 Rust 原子操作和内存顺序来避免数据竞争的示例代码:
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
let flag = Arc::new(AtomicBool::new(false));
let flag_clone = flag.clone();
let handle = thread::spawn(move || {
// 模拟一些工作
std::thread::sleep(std::time::Duration::from_secs(1));
flag_clone.store(true, Ordering::Release);
});
while!flag.load(Ordering::Acquire) {
// 等待 flag 变为 true
std::thread::yield_now();
}
handle.join().unwrap();
println!("Flag is true, safe to proceed.");
}
代码分析
-
初始化:
- 我们创建了一个
AtomicBool
类型的flag
,并使用Arc
来共享它,因为AtomicBool
本身是Sync
和Send
的,可以在线程间安全传递。
- 我们创建了一个
-
线程部分:
- 在新线程中,我们首先模拟一些工作(这里是睡眠 1 秒),然后使用
Release
顺序将flag
设置为true
。Release
顺序确保在设置flag
为true
之后的所有操作(这里没有后续操作)不会被重排到设置操作之前,并且这个设置操作的效果会被释放到内存中,以便其他线程可以观察到。
- 在新线程中,我们首先模拟一些工作(这里是睡眠 1 秒),然后使用
-
主线程部分:
- 在主线程中,我们使用
Acquire
顺序不断读取flag
。Acquire
顺序保证在读取flag
之前的所有操作(这里是循环中的yield_now
)不会被重排到读取操作之后,从而确保我们能读取到最新的flag
值。一旦flag
变为true
,主线程继续执行后续操作。
- 在主线程中,我们使用
通过这种方式,合理设置原子操作的内存顺序,我们避免了数据竞争,保证了程序在多线程环境下的正确性。