不能直接用 int 做多线程计数器,因为 counter++ 非原子,含读-改-写三步,多线程下会因竞态导致漏加;应使用 std::atomic 并注意内存序、初始化及逻辑正确性。

为什么不能直接用 int 做多线程计数器
多个线程同时执行 counter++ 会出错,因为这不是原子操作:它实际包含读取、加 1、写回三步。两个线程可能同时读到旧值,各自加 1 后写回,结果只加了一次。编译器优化或 CPU 乱序执行还可能让写入延迟可见,导致其他线程长期看不到更新。
常见错误现象包括:最终计数值远小于预期、偶发性漏加、调试时“神奇地”变正常(因调度变化掩盖了竞态)。
std::atomic<int></int> 是最直接的替代方案
它保证读、写、修改操作全部原子,且默认提供顺序一致性(std::memory_order_seq_cst),语义最接近普通变量,适合绝大多数场景。
- 声明:
std::atomic<int> counter{0};</int> - 自增:
counter.fetch_add(1);或更简洁的++counter;(重载版本也原子) - 读取当前值:
counter.load()(不要直接用counter隐式转换,虽部分编译器允许但不规范) - 避免误用:
counter = counter + 1❌ —— 这是先load再赋值,中间不原子
性能敏感时考虑放宽内存序
顺序一致性开销略高,尤其在弱一致性架构(如 ARM)上。若只是统计总数、不依赖与其他变量的同步关系,可用更轻量的内存序:
立即学习“C++免费学习笔记(深入)”;
- 纯计数累加(无依赖):
counter.fetch_add(1, std::memory_order_relaxed) - 需要确保加法对其他线程“及时可见”,但不强制全局顺序:
std::memory_order_acquire(读)/std::memory_order_release(写)配对使用 -
std::memory_order_consume极少必要,且易出错,建议回避
注意:relaxed 模式下,counter.load() 仍需显式调用,且不能假设其值“最新”——它只保证该操作自身原子,不保证与其他内存访问的顺序。
别忽略初始化和生命周期问题
std::atomic 对象必须静态或线程安全地初始化。局部变量没问题;全局/静态变量要注意:若在动态初始化阶段(如其他全局对象构造函数中)被访问,可能未完成初始化。
- 推荐初始化方式:
std::atomic<int> counter{0};</int>(常量表达式,静态初始化,绝对安全) - 避免:
std::atomic<int> counter; counter = 0;</int>—— 默认构造不初始化内部值,行为未定义 - 若计数器随对象生存期存在,确保它不被析构后访问(例如线程还在跑时对象已销毁)
最容易被忽略的是:原子变量解决的是数据竞争,不是逻辑错误。比如多个线程反复重置计数器、或把计数结果当成信号量用,这时光靠 atomic 不够,得配合条件变量或明确的状态协议。


















