
std::scoped_lock 构造时就完成全部加锁,失败则自动回滚
它不是“先锁一个、再锁下一个”,而是在构造函数里尝试一次性获取所有互斥量的所有权;只要任意一个锁不可用(比如已被其他线程持有),它会立即释放已成功获取的锁,并抛出 std::system_error(错误码为 std::errc::resource_deadlock_would_occur 仅在检测到死锁风险时触发,实际更常见的是阻塞等待或超时失败)。这意味着你不用手动写 try-catch + 解锁逻辑来防死锁。
- 必须传入可拷贝/可移动的互斥量对象(
std::mutex、std::recursive_mutex等),不能传指针或引用 - 构造成功后,析构时自动按逆序解锁(与构造时加锁顺序无关,内部已排序)
- 若使用
std::defer_lock标签,构造时不加锁,需后续调用lock()—— 这就失去了一次性防死锁的意义,慎用
为什么不能用 std::lock_guard 锁多个 mutex?
std::lock_guard 只接受单个互斥量,强行套多个会导致编译失败:error: no matching constructor。有人试图写两个 std::lock_guard,但顺序不一致就会埋下死锁隐患:
std::lock_guard<std::mutex> g1(mtx_a); // 线程1先a后b std::lock_guard<std::mutex> g2(mtx_b);
而另一处代码反着来:
std::lock_guard<std::mutex> g2(mtx_b); // 线程2先b后a → 死锁风险
-
std::scoped_lock内部调用std::lock,后者采用“先升序排列地址再尝试加锁”的策略,从语言层面规避了锁序依赖 - C++17 起才支持多参数构造;C++14 只能用
std::lock+ 多个std::lock_guard手动管理,容易漏解锁
std::scoped_lock 的超时控制:用 std::chrono 配合 try_lock_for
原生 std::scoped_lock 不支持超时,但你可以退一步用 std::unique_lock + std::defer_lock 组合实现类似效果,不过那就脱离“一次性安全锁定”的初衷了。真要超时,推荐直接上 std::scoped_lock 的替代方案:
立即学习“C++免费学习笔记(深入)”;
- 先用
std::try_to_lock标签构造std::scoped_lock,它会非阻塞尝试获取所有锁,失败则构造失败(owns_lock() == false) - 需要带超时的场景,改用
std::unique_lock分别调用try_lock_for(),再用std::lock做兜底 —— 但这手动步骤多了,容易出错 - 注意:
std::scoped_lock没有try_lock_until成员函数,别在它身上找
容易忽略的兼容性细节:递归锁、shared_mutex 和 move-only 类型
std::scoped_lock 支持 std::recursive_mutex,但不支持 std::shared_mutex 或 std::shared_timed_mutex(因为它们的 lock 接口签名不同,不满足 Lockable 概念要求)。另外,如果你传入的是 move-only 的自定义锁类型(比如包装了 std::unique_lock 的 RAII 类),它可能无法编译通过 —— 因为 std::scoped_lock 构造时会对每个参数做 copy/move,而 move-only 类型只能 move 一次。
- 确认所有互斥量类型都满足
Lockable:即提供lock()、unlock()、try_lock()且无异常 - 避免混用
std::mutex和std::shared_mutex在同一个std::scoped_lock中 —— 编译报错,错误信息通常是no matching function for call to 'lock' - 调试时留意
std::scoped_lock的生命周期:它必须比所有被锁的资源活得久,否则析构时可能访问已销毁的 mutex
std::scoped_lock 引用的——一旦 mutex 提前析构,std::scoped_lock 析构时调用 unlock() 就会 UB。









