贝利信息

c++中如何使用std::atomic实现无锁计数器_c++原子操作实例【实例】

日期:2026-01-20 00:00 / 作者:尼克
std::atomic做计数器足够安全且够用,但必须正确使用原子操作、避免取地址或memcpy、显式调用load/store、按需选择memory_order(如relaxed),且T必须trivially_copyable。

std::atomic 做计数器足够安全吗

够用,但得用对操作。直接用 +++= 是安全的,因为 std::atomic 重载了这些运算符,底层调用的是原子加法指令(如 x86 的 lock xadd)。但别把它当普通 int 用——比如取地址后传给非原子函数、或用 memcpy 拷贝,会破坏原子性保证。

常见错误现象:

std::atomic cnt{0};
int* p = &cnt;  // ❌ 危险!获取内部值地址,失去原子语义
*p = 1;         // 非原子写入,竞态风险

多线程递增时为什么还要指定 memory_order

因为不同内存序影响性能和可见性边界。计数器本身只要求“加法不丢失”,不关心和其他变量的执行顺序,这时用 std::memory_order_relaxed 就够了——CPU 不会插入多余屏障,吞吐更高。

对比示例:

std::atomic cnt{0};

// 默认:强顺序,安全但有开销 cnt++;

// 推荐(纯计数场景): cnt.fetch_add(1, std::memory_order_relaxed);

// 错误用法(混合顺序): cnt.fetch_add(1, std::memory_order_relaxed); cnt.load(std::memory_order_acquire); // acquire 和 relaxed 搭配无意义,易误导

std::atomic 能当开关用吗

能,但别用 operator=operator== 直接赋值比较——虽然语法合法,但容易写出非预期行为。例如:

std::atomic ready{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 的 T 必须满足 trivially copyable 吗

必须。否则编译失败。这意味着你不能把 std::stringstd::vector 或带虚函数/自定义构造函数的类塞进 std::atomic

错误示例:

struct NonTrivial {
    std::string s; // ❌ string 构造/析构非平凡
};
std::atomic x; // 编译错误

实际写无锁计数器,最常踩的坑不是不会调用 fetch_add,而是忘了内存序语义、误以为 atomic 能“自动同步其他变量”、或者试图原子化非 trivial 类型。这些地方一错,问题往往在高并发下才暴露,且极难复现。