原子读取为何重要?仅保证原子写入是否足够?
首先纠正你的核心误解:原子写入不会阻塞普通读取——它仅保证自身是不可分割的完整操作,不会出现写入到一半被打断的情况,但完全不干涉其他线程的普通读取行为。因此哪怕所有写入都实现了原子化,原子读取依然必不可少,核心原因有三点:
1. 缓存可见性问题
现代CPU依赖多级缓存提升性能,线程读取变量时默认优先从自身核心的缓存中取数据,而非直接访问主内存。当一个线程通过原子操作写入新值后,这个新值可能仅存在于该线程核心的缓存中,尚未同步到其他核心的缓存或主内存。若另一个线程用普通读取,会直接从自身缓存获取旧值,完全看不到原子写入的最新结果。
原子读取会触发CPU的缓存一致性协议(如MESI),强制从主内存或持有最新值的缓存中读取数据,确保拿到全局最新的原子写入结果。
举个实际场景:
- 线程A:原子写入
i = 10 - 线程B:普通读取
i,可能读到的还是初始值0(自身缓存未同步) - 线程B改用原子读取
i,则能确保读到10
2. 指令重排序的隐患
编译器和CPU为优化性能,会对普通读写指令进行重排序(只要不违反单线程逻辑),但这种优化在多线程场景下会彻底打乱预期逻辑。
比如这段代码:
// 线程A atomic_flag = true; // 原子写入 i = 10; // 假设为原子写入 // 线程B if (atomic_flag) { int val = i; // 普通读取 }
CPU可能将线程B中int val = i的指令重排到if (atomic_flag)之前,导致val读到i的旧值——哪怕i的写入是原子的。而原子读取配合合适的内存序(如std::memory_order_acquire),能禁止这种跨线程的指令重排序,保证读取操作严格在判断atomic_flag之后执行。
3. 跨平台的原子性标准保证
很多现代CPU对对齐的基本类型(如32位int)的普通读取是原子的,但这是CPU架构的特性,并非编程语言标准(如C++、Java)所承诺的。如果代码需要跨平台运行,依赖这种架构特性会埋下兼容性隐患。
原子读取是编程语言标准明确保证的原子操作,无论在何种CPU架构下,都能确保读取操作不会被打断,不会出现“读取到一半值被修改”的情况——哪怕写入是原子的,普通读取在部分架构下依然可能是非原子的。
内容的提问来源于stack exchange,提问作者Aluminate

