std::mutex + std::lock_guard 是最安全、最常用的做法;手动 lock()/unlock() 易出错,不推荐。counter++ 非原子操作,拆为读-改-写三步,引发数据竞争导致结果错误、崩溃或死锁;lock_guard 用 RAII 自动管理锁,避免泄漏。

直接说结论:std::mutex + std::lock_guard 是最安全、最常用的做法;手动调用 lock()/unlock() 容易出错,不推荐用于递增这类简单操作。
为什么 counter++ 不能直接多线程调用
它不是原子操作:实际拆成三步——读取当前值、加 1、写回内存。两个线程可能同时读到 100,各自加 1 后都写回 101,最终只增加 1 次而不是 2 次。这种丢失更新就是典型的数据竞争。
常见错误现象包括:
- 最终结果远小于预期(比如两个线程各加 100000,结果却只有 100321)
- 程序偶尔崩溃(
unlock()被未持锁线程调用,行为未定义) - 死锁(同一线程重复
lock(),或多个互斥量锁定顺序不一致)
std::lock_guard 是首选方案
它用 RAII 自动管理锁的生命周期,避免忘记 unlock() 或异常导致的锁泄漏。
立即学习“C++免费学习笔记(深入)”;
实操建议:
- 把
std::lock_guard<:mutex></:mutex>声明在临界区最开始,作用域刚好覆盖共享变量操作 - 不要跨函数传递互斥量或锁对象;
std::mutex不可复制、不可移动 - 若需提前释放锁,改用
std::unique_lock并调用unlock(),但递增场景通常不需要
示例:
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 构造即 lock
++counter; // 临界区
} // 析构自动 unlock
}
什么时候该换用 std::atomic
如果只是对整数做简单读-改-写(如 ++、fetch_add),且不需要和其他操作构成逻辑上的临界区,std::atomic 更轻量、无锁、性能更好。
但它有明确限制:
- 仅适用于基本类型(
int、bool、指针等),不能保护结构体或多个变量的组合更新 - 不提供内存序以外的同步语义,比如无法等待条件成立(这时还得靠
std::condition_variable) - 若后续要扩展为“先检查再更新”(如 if (counter
示例:
std::atomic<int> counter{0};
void increment() {
for (int i = 0; i < 1000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
}
}
容易被忽略的细节
互斥量本身不是“保护变量”的魔法开关,它只约束代码执行路径。真正容易踩坑的是:
- 把互斥量声明为局部变量(每个线程一份,完全无效)
- 在类成员函数中用
this->mtx,但对象本身生命周期没管好(比如线程还在跑,对象已被析构) - 误以为
std::mutex可递归锁 —— 同一线程两次lock()就会死锁 - 在持有锁时调用可能阻塞或抛异常的函数(如
std::cout在某些环境下可能卡住),拉长临界区,拖慢整体并发度
临界区越短越好,能只锁 ++counter 就别锁整个循环体或日志输出。


















