如何修复锁顺序反转问题?使用RAII风格锁触发死锁怎么解决
问题根因
- 全局容器锁持有时间过长:你当前的实现中,
shared_lock、lock_guard的保护区间覆盖了整个LongRoutine的执行周期,全局大锁被长时间持有,不仅会导致严重的锁竞争,还会直接引入锁顺序反转风险:如果LongRoutine内部需要获取其他业务锁,相当于你持有connectionMapMutex的同时申请其他锁,只要其他代码路径存在「先持有该业务锁、再申请connectionMapMutex」的逻辑,就会触发锁顺序反转死锁,这也是TSan上报问题的核心来源。 - shared_mutex读写饥饿放大风险:当有写锁(容器插入/删除接口持有的独占锁)进入排队队列时,后续所有申请读锁的请求都会被阻塞避免写饥饿,如果你持有读锁长时间运行业务逻辑,会导致大量读写请求全部卡在锁等待队列,进一步放大死锁触发概率。
- 锁职责不合理:全局
connectionMapMutex的职责应该是保护connections容器的线程安全,不应该用来保护单个Connection对象的业务逻辑执行,锁职责越界直接导致锁粒度不可控。
修复方案
- 缩小锁粒度,提前释放容器锁
仅在操作connections容器的代码区间持有锁,拿到需要的Connection对象后立刻释放锁,再执行LongRoutine业务逻辑。为了避免提前释放锁后Connection被onDisconnected删除导致野指针,推荐将容器存储的Connection改为std::shared_ptr包裹,保证你持有shared_ptr引用期间对象不会被释放。
修改后的代码示例:
std::unordered_map<size_t, std::shared_ptr<Connection>> connections; std::shared_mutex connectionMapMutex; void LongRoutine(Connection &connection) { // 单个Connection的内部操作,用Connection自带的成员锁保护即可,不要碰全局容器锁 } void onRTCDataMessage(RTC::Message message) { std::shared_ptr<Connection> conn; { std::shared_lock guard(connectionMapMutex); auto it = connections.find(message.targetPeer); if(it == connections.end()) { return; } conn = it->second; } // 全局容器锁在此处释放,后续业务逻辑不持有大锁 LongRoutine(*conn); } void onMessage(size_t peer, std::shared_ptr<TUSocket> socket) { std::shared_ptr<Connection> conn; { std::lock_guard<std::shared_mutex> guard(connectionMapMutex); auto [it, inserted] = connections.try_emplace(peer, std::make_shared<Connection>()); conn = it->second; } // 提前释放全局锁 LongRoutine(*conn); } void onDisconnected(size_t peer) { std::lock_guard<std::shared_mutex> guard(connectionMapMutex); connections.erase(peer); }
- 明确锁层级规则
如果业务逻辑需要同时持有多把锁,必须全局统一锁的获取顺序,比如规定「先拿Connection成员锁、再拿其他业务锁,永远不允许持有其他业务锁的时候申请全局容器锁」,所有代码路径严格遵守该顺序即可彻底避免锁顺序反转问题。
内容的提问来源于stack exchange,提问作者jeffbRTC
相关产品推荐
相关产品推荐

