C++ mutex互斥锁怎么用 C++防止多线程资源冲突教程【锁】

5次阅读

std::mutex 必须配合 std::lock_guard 或 std::unique_lock 使用,手动调用 lock()/unlock() 易致死锁;应遵循 RaiI 原则、统一多锁顺序、避免嵌套、禁用拷贝移动,并在性能敏感场景优先考虑原子操作或细粒度锁。

C++ mutex互斥锁怎么用 C++防止多线程资源冲突教程【锁】

std::mutex 必须配合 std::lock_guard 或 std::unique_lock 使用

直接调用 mutex.lock()mutex.unlock() 极易出错:忘记 unlock、异常中途退出、提前 return 都会导致死锁。c++ 标准库不鼓励手动配对,而是靠 RAII 自动管理生命周期。

正确做法是把 std::mutexstd::lock_guard(最常用)或 std::unique_lock(需要延迟加锁或转移所有权时用)一起声明在临界区作用域开头:

std::mutex mtx; int shared_value = 0;  void increment() {     std::lock_guard lock(mtx); // 构造即加锁     ++shared_value;                         // 访问共享资源 } // 析构自动 unlock —— 即使这里抛异常也安全
  • std::lock_guard 不可复制、不可移动,构造后立即加锁,析构必解锁,开销最小
  • std::unique_lock 更灵活(支持 try_lock()defer_lockunlock() 手动释放),但有轻微额外开销
  • 绝不要把 lock_guard 声明成全局或类成员变量——它必须和临界区生命周期严格对齐

多个 mutex 加锁顺序不一致会引发死锁

当一段逻辑需同时操作两个共享对象(比如转账:从 A 扣款 + 给 B 加款),若线程 1 先锁 A 再锁 B,而线程 2 先锁 B 再锁 A,就可能互相等待、永久阻塞。

解决方法不是“小心写”,而是强制统一顺序:

立即学习C++免费学习笔记(深入)”;

  • 按地址大小排序:std::lock(mtx_a, mtx_b) 是标准方案,它内部用无死锁算法批量加锁
  • 或者约定固定顺序:比如所有代码都先锁 account_mutex[smaller_id],再锁 account_mutex[larger_id]
  • 避免嵌套锁:一个函数里只锁一个 mutex;如需多资源,提取为独立函数并统一加锁顺序

错误示例(高风险):

// 线程1: mtx_a.lock(); mtx_b.lock(); // OK in Thread 1 // 线程2: mtx_b.lock(); mtx_a.lock(); // 可能死锁

std::mutex 不能跨线程拷贝或移动,别传给 Lambda 捕获值

std::mutex 是非拷贝、非移动类型,试图把它放进 std::thread 构造函数或 lambda 捕获列表时写成 [=][mtx],编译直接报错:

error: use of deleted function ‘std::mutex::mutex(const std::mutex&)’

正确方式只有两种:

  • 捕获引用:[&mtx]{ ... mtx.lock(); ... }(注意确保 mtx 生命周期长于线程)
  • 指针或引用到 std::threadstd::thread{f, &mtx},函数参数声明为 std::mutex& mtx
  • 如果用 std::async 或线程池,推荐把 mutex 定义在共享数据结构内部,由该结构的方法封装同步逻辑

性能敏感场景慎用 mutex,优先考虑无锁替代方案

每次 lock() 都可能触发系统调用或总线争抢,尤其在高并发、短临界区(比如计数器++)下,std::mutex 成为瓶颈。

可选更轻量的方案:

  • 原子操作:std::atomic counter{0}; counter.fetch_add(1); —— 无需锁,CPU 硬件保证,适合简单读写
  • 读多写少:用 std::shared_mutex(C++17)或第三方 folly::SharedMutex
  • 彻底避免共享:每个线程用局部变量统计,最后归并(如 tbb::parallel_forreduction)

但注意:原子操作不能替代锁来保护复杂不变式(例如“检查余额 > 扣款”这种两步操作),这时候仍需 mutex。

真正容易被忽略的是:mutex 的粒度。锁整个容器不如锁单个元素;锁一次处理 1000 条不如分批加锁。粗粒度锁看着简单,往往是性能隐形杀手。

text=ZqhQzanResources