std::lock_guard使用std::adopt_lock未提前加锁的异常行为问询
结论
第二种写法本身就触发未定义行为,和是否在std::lock执行前抛出异常无关,执行顺序完全不符合std::lock_guard的接口约定。
原理说明
std::lock_guard的std::adopt_lock重载构造函数有强制前置要求:当前线程必须已经持有传入互斥量的所有权。该构造函数不会主动对互斥量加锁,仅接管锁的释放权限,在lock_guard析构时自动执行解锁操作。- 第二种写法的执行顺序完全颠倒了逻辑:
- 第一步构造
guard1时,线程尚未持有m1的锁,直接传入std::adopt_lock,这一步就已经触发未定义行为,不需要等到后续std::lock执行,也不需要异常触发。 - 即使第一步的未定义行为没有立即暴露,若在构造完两个
lock_guard之后、调用std::lock之前抛出异常,两个lock_guard在栈展开析构时,会尝试对从未加锁的m1、m2执行解锁操作,这又是另一处未定义行为。
- 第一步构造
正确范式逻辑
你给出的第一种写法是符合标准约定的安全实现:
std::lock(m1, m2); std::lock_guard<std::mutex> guard1(m1, std::adopt_lock); std::lock_guard<std::mutex> guard2(m2, std::adopt_lock); // 业务逻辑
- 先调用
std::lock(m1, m2):该函数内置死锁规避逻辑,会原子性地完成两个互斥量的加锁操作,成功返回时当前线程已经同时持有m1、m2的所有权。 - 再用
std::adopt_lock构造两个lock_guard:仅负责托管已持有的锁,作用域结束时自动解锁,即使业务逻辑抛出异常也能保证锁正常释放,无安全问题。
补充说明
如果编译环境支持C++17及以上版本,可直接使用std::scoped_lock简化写法,不需要手动处理锁顺序和adopt_lock标记,更不容易出现顺序错误:
std::scoped_lock guard(m1, m2); // 业务逻辑
内容的提问来源于stack exchange,提问作者Avelino Zepeda Martinez
相关产品推荐
相关产品推荐

