std::atomic不能直接当普通int用,需用fetch_add()、load()、store()等成员函数操作;内存序应按需选择,避免过度使用seq_cst;结构体原子需检查is_lock_free();自旋锁应使用std::atomic_flag。

std::atomic 声明后不能直接当普通 int 用
很多人写 std::atomic<int> cnt = 0;</int> 后,下意识写 cnt++ 或 cnt = cnt + 1,结果编译失败。这是因为 std::atomic 不重载所有算术运算符,cnt++ 看似是原子操作,但实际是“读-改-写”三步,必须显式调用成员函数。
正确做法是用 fetch_add()、load()、store() 这类明确语义的函数:
-
cnt.fetch_add(1):加 1 并返回旧值(类似i++) -
cnt.fetch_add(1, std::memory_order_relaxed):指定内存序,避免默认的强序开销 -
cnt.load()替代cnt直接取值;cnt.store(42)替代cnt = 42 - 别写
cnt += 1—— 它依赖operator+=,而std::atomic只对部分整型和指针类型提供该重载,且行为依赖模板特化,不通用
多线程里只用 load/store 就够了?不一定
看似只是读个计数器,用 load() 就行,但实际要看场景。比如在循环中轮询等待某个条件:while (flag.load() == false) { /* busy wait */ },如果不用 std::memory_order_acquire,编译器或 CPU 可能重排后续读操作,导致逻辑错乱。
常见误判点:
立即学习“C++免费学习笔记(深入)”;
- 默认
load()和store()是std::memory_order_seq_cst(最强序),安全但有性能代价 - 纯计数场景(如统计请求数)用
std::memory_order_relaxed足够,它允许最大优化,且不破坏计数值本身正确性 - 做同步信号(如生产者设 flag,消费者等 flag)必须配对使用 acquire/release,否则无法保证先行发生关系
-
fetch_add()默认也是 seq_cst,高并发计数时建议显式传std::memory_order_relaxed
std::atomic 不能当锁用,std::atomic_flag 才是轻量自旋锁
有人想用 std::atomic<bool> locked{false};</bool> 实现自旋锁:while (locked.exchange(true)) {},这看似可行,但 std::atomic<bool></bool> 不保证无锁(lock-free),某些平台会回退到内部互斥量,反而更慢。
真正适合手写自旋锁的是 std::atomic_flag:
- 它是 C++ 唯一保证 lock-free 的原子类型,初始化必须用
ATOMIC_FLAG_INIT(C++20 起可用构造函数) - 只有
test_and_set()和clear()两个函数,语义清晰,无歧义 -
test_and_set()默认是 acquire 操作,clear()默认是 release,天然适配锁语义 - 别给
std::atomic_flag赋值或比较 —— 它不支持operator==或=,只能靠test_and_set()的返回值判断
std::atomic 在结构体里失效?得用 is_lock_free() 检查
把 std::atomic<mystruct></mystruct> 当作整体原子更新,很容易翻车。C++ 只对 trivially copyable 类型提供 std::atomic 特化,而且是否 lock-free 取决于大小和平台对齐。
比如一个 32 字节的结构体,在 x86-64 上大概率不是 lock-free,load() 会变成 mutex 加锁,性能暴跌且可能死锁(尤其在 signal handler 里调用)。
- 用
my_atomic.is_lock_free()运行时检查,或std::atomic<mystruct>::is_always_lock_free</mystruct>编译期断言 - 超过指针宽度(通常 8 字节)的类型,基本别指望硬件原子指令支持
- 真要原子更新结构体,优先考虑用
std::atomic<:shared_ptr>></:shared_ptr>或拆成多个独立std::atomic字段 - 别依赖
std::atomic的隐式转换 ——std::atomic<int></int>不能自动转成int,必须显式.load()
最常被忽略的是:内存序不是“越强越好”,而是“够用就行”。用错顺序不会报错,但可能让程序在某台机器上偶然跑通,换一台就出竞态 —— 这种问题最难复现。











