直接用 int 做计数器在多线程下会出错,因为 ++counter 非原子,包含读取、加1、写回三步,可能导致两个线程读到同一旧值而只增1;应使用 std::atomic 保证原子性,复杂逻辑则需 std::mutex。

为什么直接用 int 做计数器在多线程下会出错
因为 ++counter 不是原子操作:它实际包含“读取、加1、写回”三步。两个线程同时执行时,可能都读到旧值,各自加1后写回,结果只+1而不是+2。现象是最终值远小于预期,且每次运行结果可能不同。
常见错误写法:
int counter = 0;
// 多个线程调用
void increment() { ++counter; } // ❌ 非线程安全
用 std::atomic<int></int> 是最轻量可靠的解法
它保证读-改-写操作原子性,底层通常编译为单条 CPU 指令(如 xadd),无锁且高效。
-
std::atomic<int></int>默认构造即初始化为 0,无需额外同步 - 支持
load()、store()、fetch_add()、operator++等,语义清晰 - 注意:默认使用
memory_order_seq_cst(强顺序),对绝大多数场景足够且安全 - 避免直接对
std::atomic变量取地址或做非原子访问(如&counter后用++*ptr)
示例:
std::atomic<int> counter{0};
void increment() { counter.fetch_add(1, std::memory_order_relaxed); }
// 或更直观的
void increment_simple() { ++counter; }</int>
什么时候该用 std::mutex 而不是 std::atomic
当计数逻辑超出简单增减——比如“仅当值小于100才加1”,或需要和其它变量一起更新时,std::atomic 就不够用了。
-
std::atomic只能保护单个变量;复合判断+修改必须用互斥锁 - 用
std::mutex时,务必确保所有访问路径都加锁,包括读取(否则可能读到中间态) - 别在锁内做耗时操作(如 I/O、网络调用),否则拖慢所有线程
- 推荐用
std::lock_guard<:mutex></:mutex>自动管理,避免忘记 unlock
示例:
std::mutex mtx;
int counter = 0;
void safe_increment_if_under(int limit) {
std::lock_guard<:mutex> lock(mtx);
if (counter
<h3>别忽略初始化和生命周期问题</h3>
<p>全局或静态 <code>std::atomic</code> 安全,但局部静态或栈上对象要注意:如果计数器在某个线程中构造、在另一个线程中首次访问,C++11 保证静态局部变量的初始化是线程安全的;但非静态局部 <code>std::atomic</code> 对象若被多个线程共享(比如通过指针传递),其析构时机就可能引发竞态。</p>
<ul>
<li>优先把计数器作为类成员或全局/静态变量,明确生命周期</li>
<li>避免在 lambda 捕获中隐式共享栈上 <code>std::atomic</code> 变量,除非你确定它的生存期覆盖所有线程执行时间</li>
<li>如果计数器需跨 DLL 边界或与 C 代码交互,注意 <code>std::atomic</code> 的 ABI 兼容性(一般没问题,但极端情况下需验证)</li>
</ul>
<p>真正容易被忽略的是:原子变量本身线程安全,但它的**使用上下文**不一定安全——比如用它控制一个非原子的资源池大小,却忘了池里对象的分配/释放也需要同步。</p></:mutex>C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











