std::mutex 和 std::lock_guard 是 c++ 多线程中安全同步共享资源的核心工具:前者提供手动加锁/解锁接口,后者通过 RaiI 自动管理锁的生命周期,确保异常安全。

在多线程 C++ 程序中,std::mutex 和 std::lock_guard 是最常用、最安全的同步工具之一,用于保护共享资源不被多个线程同时修改,避免数据竞争(data race)和未定义行为。
mutex 用来上锁,lock_guard 自动管理锁的生命周期
std::mutex 是一个底层互斥量对象,提供 lock() 和 unlock() 手动控制接口;但手动管理容易出错(比如忘记 unlock、异常提前退出导致死锁)。std::lock_guard 是 RAII 封装器:构造时自动加锁,析构时自动解锁(哪怕发生异常也会释放),非常可靠。
- 必须用
std::mutex对象的引用初始化lock_guard,不能传值 -
lock_guard是不可复制的(deleted copy constructor),只能移动或栈上构造
典型用法:保护共享变量或临界区
假设多个线程要累加同一个 int 变量:
std::mutex mtx; int shared_counter = 0; void increment() { std::lock_guard guard(mtx); // 构造即加锁 ++shared_counter; // 访问共享资源 } // guard 离开作用域,自动调用 ~lock_guard() → 解锁
- 所有访问
shared_counter的地方都必须用同一把mtx加锁 - 锁的作用域越小越好,只包裹真正需要保护的代码段
- 不要跨函数持有锁(比如把 lock_guard 传给另一个函数),会延长临界区,降低并发性
常见错误和注意事项
容易忽略的细节会影响正确性和性能:
立即学习“C++免费学习笔记(深入)”;
- 全局或静态
mutex要确保初始化顺序安全;推荐直接定义(C++11 起保证静态局部变量线程安全初始化) - 避免嵌套加锁同一 mutex(会导致死锁),如已持锁再调
lock()是未定义行为;可用std::recursive_mutex(但通常说明设计有问题) - 不要把
lock_guard声明在 if 分支外却只在分支内使用——它必须在需要保护的代码前构造 - 若需尝试加锁或带超时,用
std::unique_lock替代,lock_guard不支持这些操作
完整可运行示例
启动两个线程并发调用 increment() 10 万次,最终结果应为 200000:
#include #include #include #include std::mutex mtx; int counter = 0; void increment() { for (int i = 0; i < 100000; ++i) { std::lock_guard guard(mtx); ++counter; } } int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Final counter: " << counter << "n"; // 输出 200000 }
去掉 lock_guard 或 mtx,结果大概率小于 200000,甚至每次不同——这就是典型的竞态现象。