std::atomic做计数器足够安全且够用,但必须正确使用原子操作、避免取地址或memcpy、显式调用load/store、按需选择memory_order(如relaxed),且T必须trivially_copyable。
够用,但得用对操作。直接用 ++ 或 += 是安全的,因为 std::atomic 重载了这些运算符,底层调用的是原子加法指令(如 x86 的 lock xadd)。但别把它当普通 int 用——比如取地址后传给非原子函数、或用 memcpy 拷贝,会破坏原子性保证。
常见错误现象:
std::atomiccnt{0}; int* p = &cnt; // ❌ 危险!获取内部值地址,失去原子语义 *p = 1; // 非原子写入,竞态风险
load()、store()、fetch_add()、operator++ 等成员函数访问int x = cnt;,而用 int x = cnt.load(); 显式表达意图std::memory_order_seq_cst,安全但稍慢;高并发场景可考虑 relaxed(仅计数,不依赖顺序)因为不同内存序影响性能和可见性边界。计数器本身只要求“加法不丢失”,不关心和其他变量的执行顺序,这时用 std::memory_order_relaxed 就够了——CPU 不会插入多余屏障,吞吐更高。
对比示例:
std::atomiccnt{0}; // 默认:强顺序,安全但有开销 cnt++;
// 推荐(纯计数场景): cnt.fetch_add(1, std::memory_order_relaxed);
// 错误用法(混合顺序): cnt.fetch_add(1, std::memory_order_relaxed); cnt.load(std::memory_order_acqui
re); // acquire 和 relaxed 搭配无意义,易误导
relaxed:只保证该原子操作自身不被重排,不建立同步关系acquire/release:用于保护临界资源(如指针解引用前需确保对象已构造完成),计数器一般不需要seq_cst
能,但别用 operator= 或 operator== 直接赋值比较——虽然语法合法,但容易写出非预期行为。例如:
std::atomicready{false}; // ❌ 危险写法(可能被编译器优化掉读取): while (!ready) { / busy wait / }
// ✅ 正确写法: while (!ready.load(std::memory_order_acquire)) { / ... / }
// ✅ 更推荐(带提示,减少空转功耗): while (!ready.load(std::memory_order_acquire)) { std::this_thread::yield(); }
std::atomic 不支持 ++,也不支持算术操作,只适合标志位store() 通常用 release,load() 用 acquire,构成同步对std::atomic_flag 更轻量(无锁保证更强),但只能做 test-and-set,不如 bool 直观必须。否则编译失败。这意味着你不能把 std::string、std::vector 或带虚函数/自定义构造函数的类塞进 std::atomic。
错误示例:
struct NonTrivial {
std::string s; // ❌ string 构造/析构非平凡
};
std::atomic x; // 编译错误
int、long long)、enum、POD 结构体(无构造函数、无虚函数、所有成员 trivial)可以std::is_trivially_copyable_v
std::atomic<:shared_ptr>> 或配合 mutex,别硬套 atomic
实际写无锁计数器,最常踩的坑不是不会调用 fetch_add,而是忘了内存序语义、误以为 atomic 能“自动同步其他变量”、或者试图原子化非 trivial 类型。这些地方一错,问题往往在高并发下才暴露,且极难复现。