单线程持有同一boost::shared_mutex多把读锁引发问题咨询
看起来你遇到了boost::shared_mutex递归加读锁带来的典型坑——最常见的就是自死锁:当同一个线程多次获取读锁后,如果后续尝试获取写锁,写锁需要等待所有读锁(包括当前线程持有的那些)释放,但当前线程被挂起等待写锁,根本没法释放已有的读锁,直接卡死。另外重复加锁也可能带来不必要的性能开销,或者掩盖代码设计上的冗余。
下面给你几个实用的解决思路:
1. 重构代码,避免递归加锁
这是最推荐的方案,从根源上解决问题。把需要共享访问的数据操作和递归逻辑拆分开:将实际的递归处理逻辑放在无锁的私有函数里,只在公共入口处加一次读锁,递归时直接调用无锁版本,避免重复加锁。
示例代码:
class SharedData { private: using Lock = boost::shared_mutex; using ReadLock = boost::shared_lock<Lock>; using WriteLock = boost::unique_lock<Lock>; Lock mutex_; std::vector<int> data_; // 无锁的递归处理函数,只负责业务逻辑 void recursiveProcessImpl(int idx) { if (idx >= data_.size()) return; // 处理data_[idx]的逻辑 recursiveProcessImpl(idx + 1); } public: void startRecursiveProcess() { ReadLock lock(mutex_); // 仅在入口加一次读锁 recursiveProcessImpl(0); } };
2. 改用递归共享互斥量
如果重构代码成本太高,可以直接替换成boost::recursive_shared_mutex,它专门支持同一线程多次获取共享锁,甚至允许在持有共享锁的前提下获取独占锁(不过升级为写锁时,如果有其他线程持有读锁,还是会阻塞)。
调整锁的定义即可:
using Lock = boost::recursive_shared_mutex; using ReadLock = boost::shared_lock<Lock>; using WriteLock = boost::unique_lock<Lock>;
⚠️ 注意:递归锁会带来额外的性能开销,而且容易掩盖代码设计上的冗余,除非万不得已,不建议优先用这个方案。
3. 手动跟踪线程内的锁状态
你可以用线程局部存储(TLS)来跟踪当前线程持有读锁的次数,只在第一次进入时加锁,后续递归调用时直接递增计数器,退出时递减,直到计数器归0再释放锁。
示例代码(记得处理异常安全,最好用RAII包装):
class SharedData { private: using Lock = boost::shared_mutex; using ReadLock = boost::shared_lock<Lock>; using WriteLock = boost::unique_lock<Lock>; Lock mutex_; std::vector<int> data_; thread_local static int read_lock_counter_; // 线程局部计数器 // 手动管理锁的获取 void acquireReadLock() { if (read_lock_counter_ == 0) { mutex_.lock_shared(); } read_lock_counter_++; } // 手动管理锁的释放 void releaseReadLock() { read_lock_counter_--; if (read_lock_counter_ == 0) { mutex_.unlock_shared(); } } public: void recursiveFunction() { acquireReadLock(); try { // 业务逻辑,递归调用recursiveFunction() recursiveFunction(); } catch (...) { releaseReadLock(); throw; } releaseReadLock(); } }; // 初始化线程局部变量 thread_local int SharedData::read_lock_counter_ = 0;
另外要特别提醒:如果你的场景中存在“先持有读锁,再尝试获取写锁”的情况,就算用了递归共享互斥量,也可能触发死锁(因为写锁需要等待所有读锁释放)。这种场景下,你需要先释放所有读锁再尝试获取写锁,或者使用boost::upgrade_lock来安全地升级锁——先获取升级锁,再升级为独占锁,这样可以避免自死锁,但升级过程中如果有其他线程持有读锁,还是会阻塞。
内容的提问来源于stack exchange,提问作者Bill Kotsias

