std::atomic仅保证单变量读写原子性,非万能锁;需配合memory_order解决内存序问题,避免重排导致数据未初始化;慎用伪失败的compare_exchange_weak,false sharing需对齐隔离。

std::atomic 不是线程安全的万能锁
它只是对单个变量的读写提供原子性保证,不是自动保护整个逻辑块。比如两个 std::atomic 变量之间的操作(如先读 A 再根据 A 更新 B)依然可能被其他线程打断,这不是 std::atomic 能解决的。
常见错误现象:data_ready 是 std::atomic,但生产者写完数据后才设为 true,消费者却没加内存序约束,导致看到 true 却读到未初始化的数据 —— 这是编译器重排或 CPU 乱序导致的,不是原子性失效,而是内存序缺失。
- 必须配合内存序(
memory_order)使用,最常用的是memory_order_acquire(读)和memory_order_release(写) -
memory_order_relaxed仅保证原子性,不约束前后指令顺序,适合计数器等无依赖场景 - 不要用
std::atomic模拟自旋锁:它不提供等待语义,应改用std::atomic_flag+test_and_set()
std::atomic_flag 是唯一保证无锁(lock-free)的原子类型
C++ 标准只要求 std::atomic_flag 必须是 lock-free 的,其它 std::atomic 类型是否 lock-free 取决于平台和大小。比如在 x86-64 上 std::atomic 通常是 lock-free,但在某些嵌入式平台可能退化为内部加锁实现。
使用场景:实现自旋锁、轻量级信号量、无锁队列的 head/tail 指针更新。
立即学习“C++免费学习笔记(深入)”;
- 检查是否 lock-free:
flag.is_lock_free()或ATOMIC_FLAG_INIT初始化后调用is_lock_free() - 必须用
ATOMIC_FLAG_INIT初始化,不能用= {}或默认构造 -
test_and_set()默认是memory_order_seq_cst,如需性能可显式传入memory_order_acquire等
compare_exchange_weak 和 compare_exchange_strong 的区别不是“弱”和“强”,而是“允许伪失败”
compare_exchange_weak 在某些平台(尤其是 LL/SC 架构如 ARM、RISC-V)可能因外部干扰(如中断、缓存同步)返回 false,即使当前值等于期望值。这不是 bug,是硬件特性。
错误做法:把 compare_exchange_weak 当成一次性的条件更新,不重试就继续往下走。
- 几乎所有循环 CAS 场景都该用
compare_exchange_weak,因为伪失败成本低,且循环本身已处理重试逻辑 -
compare_exchange_strong保证“值相等就一定成功”,但可能更慢(x86 上两者汇编相同,ARM 上 strong 可能多几条指令) - 非循环场景(如只试一次)才考虑
strong,但要意识到它仍可能失败(比如值本来就不等)
无锁编程 ≠ 没有同步开销,反而更容易写出正确但低效的代码
原子操作本身比普通读写贵得多:x86 上 mov 是 1 cycle,lock xadd 是 20–100+ cycles;更麻烦的是 cache line bouncing —— 多个线程频繁修改同一 cache line 中的不同原子变量,会反复使彼此的 cache line 无效。
典型坑:std::atomic 放在一起,三个变量落在同一个 64 字节 cache line 内,A 线程改 a,B 线程改 b,也会互相拖慢。
- 用
alignas(64)对齐每个原子变量,或插入char padding[64]避免 false sharing - 别为了“无锁”而无锁:如果临界区短、竞争低,
std::mutex实际更快,也更易维护 - 调试困难:竞态不会稳定复现,
TSAN可检测部分问题,但无法覆盖所有内存序误用
真正难的从来不是怎么写原子操作,是怎么画清楚变量间的依赖关系、确定哪些顺序必须保留、哪些可以放松 —— 这些没法靠查文档解决,得靠对硬件模型和具体场景的双重理解。











