多线程场景下Atomic包装类型与原生类型的差异问询
多线程场景下Atomic类型与原生类型的可见性差异
1. 其他线程能否看到非原子类型的修改?
先看你给出的非原子类型示例代码:
fn main() { let mut counter = 0; std::thread::scope(|scope| { scope.spawn(|| counter += 1) }); println!("{counter}"); }
这段代码属于未定义行为,而且完全无法保证主线程能读到子线程修改后的1值。原因在于:
- 非原子类型的读写没有同步机制,处理器会为每个线程缓存数据,子线程对
counter的修改可能只停留在自身缓存,未同步到主内存; - 即使修改同步到主内存,主线程也可能继续使用自身缓存里的旧值;
- 编译器还可能因无法感知线程间同步,对代码做重排序或优化,比如直接将初始值0保留在寄存器中,完全不读取主内存。
只有原子类型配合合适的内存顺序,才能保证线程间的可见性。比如你给出的原子类型示例:
fn main() { let counter = AtomicI32::new(0); std::thread::scope(|scope| { scope.spawn(|| counter.store(1, Ordering::Release)) }); println!("{}", counter.load(Ordering::Acquire)); // Ordering::Acquire to prevent from reordering previous instructions }
这里的Release/Acquire内存顺序配对,能确保:
- 子线程中
store操作之前的所有写操作,在主线程执行load后都可见; store的1值一定会被主线程读到——Release会强制将修改同步到主内存,Acquire会让主线程从主内存读取最新值,同时禁止指令重排序破坏可见性。
2. Ordering类型是否会影响store操作的值对其他线程的可见时机?
看你给出的Relaxed内存顺序示例:
fn main() { let counter = AtomicI32::new(0); std::thread::scope(|scope| { scope.spawn(|| counter.store(1, Ordering::Relaxed)) }); println!("{}", counter.load(Ordering::Relaxed)); }
首先明确:Ordering::Relaxed只保证原子操作的完整性(不会读到半写的中间值),不保证可见性的即时性。
具体到这个示例,无法确保一定会输出1。因为Relaxed没有内存屏障,子线程的store操作可能被处理器延迟写入主内存,主线程的load可能仍读取旧值。虽然原子类型保证修改最终会同步到主内存(不会像非原子类型那样永远不可见),但没有任何机制保证主线程执行load时能看到这个修改。
如果需要确保其他线程能及时看到修改,必须使用更强的内存顺序,比如Release/Acquire配对,或者Ordering::SeqCst(顺序一致性,最严格的内存顺序)。
内容的提问来源于stack exchange,提问作者zexed640
相关产品推荐
相关产品推荐

