在C++中,原子操作(Atomic Operation) 是不可分割的最小执行单元,能够保证在多线程环境下对共享数据的操作不会出现中间状态,从而避免竞态条件(Race Condition)。其底层实现结合了硬件支持和编译器优化。
一、原子操作的核心特性
- 不可分割性 操作要么完全执行,要么不执行,其他线程无法观察到中间状态。 示例:
std::atomic<int> x = 0;
x.fetch_add(1); // 原子性递增,不会被其他线程打断
- 内存顺序控制 通过内存屏障(Memory Barrier)约束指令重排序,保证多线程间的可见性。 *内存序选项:
memory_order_relaxed(无顺序约束)memory_order_acquire(读操作前的指令不能重排到读之后)memory_order_release(写操作后的指令不能重排到写之前)memory_order_seq_cst(严格顺序一致性,默认选项)
二、原子操作的硬件实现
1. CPU指令级支持
- x86架构:
LOCK前缀指令(如LOCK XCHG)锁定总线,确保原子性。- 现代CPU通过缓存一致性协议(如MESI)优化,避免总线锁定。
; x86原子递增示例
lock add dword [rdi], 1 ; 原子性增加内存中的值
- ARM架构: 使用
LDREX(加载-独占)和STREX(存储-独占)指令实现原子操作:
retry:
ldrex r0, [r1] ; 独占加载
add r0, r0, #1 ; 修改值
strex r2, r0, [r1] ; 尝试独占存储
cmp r2, #0 ; 判断是否成功
bne retry ; 失败则重试
2. 缓存一致性协议
- MESI协议: 通过标记缓存行状态(Modified/Exclusive/Shared/Invalid),确保多核CPU对同一内存位置的原子操作可见性。
三、C++中的原子操作实现
1. 编译器与库的协作
- 编译器优化: 将C++原子操作(如
std::atomic::load/store)转换为底层硬件指令。 - 无锁实现: 若硬件不支持特定原子操作(如双字CAS),编译器可能通过重试循环模拟。
2. 示例:自旋锁实现
class SpinLock {
std::atomic<bool> flag{false};
public:
void lock() {
bool expected = false;
while (!flag.compare_exchange_weak(expected, true,
std::memory_order_acquire, std::memory_order_relaxed)) {
expected = false; // CAS失败后重置expected
}
}
void unlock() {
flag.store(false, std::memory_order_release);
}
};
四、原子操作 VS 非原子操作
1. 非原子操作的风险
int x = 0; // 非原子变量
x++; // 编译为多条指令:LOAD → ADD → STORE
- 多线程执行时可能发生交错:
Thread1: LOAD x (0) → ADD → STORE x (1)
Thread2: LOAD x (0) → ADD → STORE x (1)
最终结果可能为1(期望为2)。
2. 原子操作的优势
std::atomic<int> x{0};
x.fetch_add(1, std::memory_order_relaxed); // 保证结果正确
五、原子操作的适用场景
- 无锁数据结构:队列、栈、计数器等。
- 标志位控制:线程退出标志、状态标记。
- 性能优化:替代互斥锁,减少上下文切换开销。
六、性能注意事项
- 缓存行争用: 频繁修改同一缓存行的原子变量会导致性能下降(伪共享)。 解决方案:
struct alignas(64) AlignedAtomic { // 64字节对齐(缓存行大小)
std::atomic<int> x;
};
- 内存序选择: 宽松内存序(
relaxed)可提升性能,但需谨慎处理依赖关系。
总结
原子操作的底层本质是:
- 硬件原子指令(如x86的
LOCK前缀、ARM的LDREX/STREX) - 缓存一致性协议(如MESI)
- 编译器与操作系统的协作
理解原子操作是掌握多线程编程、无锁编程和同步机制(如锁、信号量)的基础。
4578

被折叠的 条评论
为什么被折叠?



