C++使用adopt_lock实现标准库锁升级的方式是否合法安全?
结论:这段实现既不合法也不安全,属于典型的未定义行为,绝对不要在生产环境使用。
问题分析
- 首先违反
std::unique_lock的std::adopt_lock构造语义:std::adopt_lock的约定是调用线程已经取得了目标互斥量的独占所有权,但你当前线程持有的只是std::shared_mutex的共享读锁,完全不满足前提,直接触发未定义行为。 - 存在必然的死锁风险:如果有两个或以上线程同时持有读锁,都尝试用这种方式升级为写锁,所有线程都会阻塞等待其他线程释放读锁,永远无法继续执行,直接死锁。
- 锁计数管理错误:
std::unique_lock用std::adopt_lock构造后,析构时会自动调用一次unlock()释放独占锁,加上你手动调用的rlock.unlock(),最终互斥量的释放次数会超过持有次数,进一步触发未定义行为。
通用实践方案
这类需要读锁升级写锁的场景,行业内主流有三种可行方案:
- 先释放读锁再申请写锁(最常用)
这是成本最低、适配性最高的方案,唯一需要注意的是释放读锁到拿到写锁的间隙,数据可能被其他线程修改,拿到写锁后必须重新校验修改条件:
std::shared_mutex mtx; std::shared_lock rlock(mtx); // 读取数据判断是否需要修改 bool need_update = check_data(); if (need_update) { rlock.unlock(); std::unique_lock wlock(mtx); // 必须二次校验,防止间隙内数据被其他线程修改 if (check_data_still_need_update()) { // 执行数据修改逻辑 } }
- 使用Boost库的
upgrade_lock
如果业务要求锁升级必须是原子操作,不允许中间有其他线程修改数据,可以直接引入Boost.Thread库的upgrade_lock,它原生支持安全的锁升级逻辑:同一时间只允许一个线程持有升级锁,其他线程可以同时持有普通读锁,持有升级锁的线程可以原子升级为独占写锁,不会出现死锁问题。 - 自行实现支持升级的共享互斥量
如果不想引入Boost依赖,可以自己封装支持升级特性的互斥量,核心逻辑是:新增一个升级特权标记,同一时间只允许一个线程进入升级等待队列,拿到特权的线程等待所有读锁释放后直接拿到写锁,从机制上避免多个线程同时升级触发的死锁。
内容的提问来源于stack exchange,提问作者Lorah Attkins
相关产品推荐
相关产品推荐

