面试题答案
一键面试顺序一致性对原子操作执行顺序与可见性的影响
- 执行顺序:在Rust中,顺序一致性(
SeqCst
)是最严格的内存顺序。它确保所有线程对原子操作的执行顺序达成一致,就好像这些操作是按照一个全局顺序依次执行的。这意味着在SeqCst
顺序下,原子操作不仅在当前线程内按代码顺序执行,而且在所有线程间也有一个统一的顺序。 - 可见性:
SeqCst
模式保证一个线程对原子变量的修改,对其他所有线程都是立即可见的。这种可见性是基于严格的全局顺序,其他线程一旦看到一个SeqCst
操作,就意味着它也能看到该操作之前的所有SeqCst
操作对共享内存的修改。
不同顺序一致性模式下原子操作的行为差异
SeqCst
(顺序一致性):- 行为:所有线程对
SeqCst
原子操作有统一的顺序。它提供了最强的同步保证,但性能开销相对较大。 - 示例:
- 行为:所有线程对
use std::sync::atomic::{AtomicUsize, Ordering};
fn main() {
let shared = AtomicUsize::new(0);
let handle = std::thread::spawn(|| {
shared.store(1, Ordering::SeqCst);
});
handle.join().unwrap();
assert_eq!(shared.load(Ordering::SeqCst), 1);
}
在这个例子中,线程先存储值1
,主线程再加载值,由于SeqCst
顺序,主线程一定能看到存储的值1
。
2. Release
:
- 行为:一个线程对原子变量进行
Release
存储操作时,保证该线程在此之前对内存的所有写操作,在Release
存储操作完成后,对其他线程变得可见。但不保证其他线程看到这些写操作的顺序与当前线程一致。 - 示例:
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
fn main() {
let data = &mut [0; 10];
let flag = AtomicUsize::new(0);
let handle = thread::spawn(|| {
data[0] = 42;
flag.store(1, Ordering::Release);
});
while flag.load(Ordering::Acquire) == 0 {
thread::yield_now();
}
assert_eq!(data[0], 42);
}
在这个例子中,子线程先修改数组data
,然后进行Release
存储操作更新flag
。主线程通过Acquire
加载flag
,一旦看到flag
为1
,就能保证看到子线程对data
的修改。
3. Acquire
:
- 行为:一个线程对原子变量进行
Acquire
加载操作时,保证该线程在此之后对内存的所有读操作,能看到在Acquire
加载操作之前其他线程对共享内存的所有写操作。 - 示例:同
Release
示例,主线程Acquire
加载flag
,确保看到子线程之前的写操作(对data
的修改)。
Release
和Acquire
一起使用,能提供比SeqCst
更宽松的同步保证,在性能敏感场景中可以减少同步开销。