首页 > 新闻中心 > 技术百科

c++中如何使用mutex互斥锁_c++多线程同步机制【详解】 返回列表

尼克2026-01-15 00:00:00编辑发布,已经有个小可爱看过这篇文章啦
std::mutex必须用RAII封装,禁用裸lock/unlock;多锁需用std::scoped_lock按地址序加锁防死锁;mutex不可拷贝,须传引用或指针;递归需求应显式用std::recursive_mutex而非默认mutex。

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

直接调用 mutex.lock()

mutex.unlock() 极易出错:忘记 unlock、异常中途跳出、提前 return 都会导致死锁。C++ 标准库不鼓励裸调用,而是要求 RAII 封装。

正确做法是让锁的生命周期绑定到作用域:

std::mutex mtx;
void safe_increment() {
    std::lock_guard lock(mtx);  // 构造即 lock
    counter++;                              // 异常安全:析构自动 unlock
}

如果需要手动控制加锁时机(比如尝试加锁、延迟加锁、转移所有权),才用 std::unique_lock;日常保护临界区,std::lock_guard 更轻量、更安全。

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

当两个线程分别以不同顺序对同一组 mutex 加锁时,比如线程 A 先 lock mtx_a 再 lock mtx_b,线程 B 反过来先 lock mtx_b 再 lock mtx_a,就可能互相等待,永久阻塞。

解决方法只有两种:

  • 始终按固定地址顺序加锁:用 std::scoped_lock(C++17 起),它自动按地址升序加锁,避免死锁
  • 或手写比较逻辑,统一约定加锁顺序(如总是先 lock 地址小的那个)
std::mutex mtx_a, mtx_b;
// ✅ 推荐:用 scoped_lock 自动规避死锁
void transfer() {
    std::scoped_lock lock(mtx_a, mtx_b);  // 自动排序,安全
    // ... 操作
}

std::mutex 不可拷贝、不可复制,只能移动(且通常不移动)

std::mutex 删除了拷贝构造函数和拷贝赋值运算符,试图 std::mutex m2 = m1; 或传值给函数会编译失败。

常见误用场景:

  • std::mutex 成员放进容器(如 std::vector<:mutex>)→ 编译不过
  • 在 lambda 中按值捕获 mutex → 错误:lambda 尝试拷贝
  • std::mutex 给线程函数作参数 → 必须用 std::ref(mtx) 或指针

正确做法:用指针或引用传递,或把 mutex 设为类成员并确保生命周期长于所有线程。

递归锁不是 std::mutex,要用 std::recursive_mutex

std::mutex 是非递归的:同一线程重复调用 lock() 会导致未定义行为(通常是死锁)。如果你确实需要“同一线程可重入”,必须显式使用 std::recursive_mutex

但注意:递归锁是性能开销更大的例外,不是默认选择。90% 的同步需求里,出现“需要递归加锁”往往说明设计有问题——比如临界区划分过粗、函数职责不清晰、或没拆分好共享状态。

示例对比:

// ❌ 危险:同一线程两次 lock 同一个 std::mutex
std::mutex mtx;
mtx.lock();
mtx.lock(); // UB!

// ✅ 如需重入,改用 recursive_mutex
std::recursive_mutex rmtx;
rmtx.lock();
rmtx.lock(); // OK
rmtx.unlock();
rmtx.unlock();
实际项目中,最常被忽略的是 std::scoped_lock 的自动死锁防护能力,以及把 mutex 当普通对象随意拷贝的编译错误——这两点卡住新手最多。
  • ai
  • 的是
  • 封装
  • 最多
  • 线程
  • 多线程
  • 多个
  • 两种
  • 如果你
  • 更大

热门新闻

来电咨询