QMutexLocker通过RAII机制在构造时自动加锁、析构时自动解锁,彻底解决手动lock/unlock易遗漏、异常不安全、多分支维护难等问题,确保锁的生命周期与作用域严格绑定。

QMutex.lock() 和 unlock() 必须严格成对调用
手动加解锁是最基础也最容易出错的方式。lock() 会阻塞当前线程直到锁可用,unlock() 则释放它。但一旦临界区里发生异常、提前 return 或逻辑分支遗漏,unlock() 就不会执行——后续所有线程在 lock() 处永久挂起。
常见错误现象:QMutex: destroying locked mutex 断言失败,或程序卡死在某个线程的 lock() 调用上。
- 永远不要在函数多个
return分支中手动写unlock(); - 避免在
try/catch块里只在try末尾unlock(),异常抛出会跳过它; - 非递归模式(默认)下,同一线程重复调用
lock()会直接死锁,不是报错而是挂住; - 构造
QMutex时显式指定模式更安全:QMutex mutex(QMutex::NonRecursive)或QMutex mutex(QMutex::Recursive)。
QMutexLocker 是更安全的 RAII 封装
QMutexLocker 在构造时自动 lock(),析构时自动 unlock(),彻底规避“漏解锁”问题。它不改变锁语义,只解决生命周期管理。
使用场景:任何需要保护局部临界区的函数,尤其是含异常、多分支、或嵌套调用的场合。
- 声明方式:
QMutexLocker locker(&mutex);,作用域结束自动解锁; - 不能跨函数传递
QMutexLocker对象本身(析构时机不可控),但可传&mutex给其他函数再建新locker; - 如果需临时释放锁再重入,用
locker.unlock()+locker.relock(),但要确保重入前没被别的线程抢走资源; - 注意:
QMutexLocker构造失败(如传入空指针)会导致未定义行为,务必确认mutex已初始化。
QMutex::tryLock() 适合非阻塞等待或超时控制
当不想让线程卡死在锁上时,tryLock() 是唯一选择。它返回 bool 表示是否成功获取锁,不阻塞。
典型用途:轮询检查、避免死锁的锁顺序尝试、带超时的资源访问。
-
tryLock()立即返回,适合快速试探; -
tryLock(int timeout)最多等待timeout毫秒,超时返回false,比纯lock()更可控; - 不要用
while(!mutex.tryLock()) { QThread::msleep(1); }替代lock()——这是忙等,浪费 CPU,且可能饿死其他线程; - 用
tryLock()获取锁后,仍必须配对调用unlock(),否则照样导致死锁。
递归锁 QMutex::Recursive 不是万能解药
递归模式允许同一线程多次 lock(),对应次数的 unlock() 后才真正释放。但它掩盖了设计问题,且有性能开销。
容易踩的坑:
- 递归锁无法防止不同线程间的竞争,只解决同一线程重入;
- 误以为“用了递归锁就不用管锁顺序”,结果在多线程调用链中引发锁顺序死锁;
- 调试困难:死锁时堆栈里可能看到同一锁被多次 acquire,但看不出哪一层没配对 unlock;
- 非必要别用——90% 的递归需求其实该拆成独立函数或改用信号量/读写锁。
真正需要递归锁的场景极少,比如深度嵌套的日志记录器、或封装良好的递归算法模块,且整个调用链都由单一线程驱动。


















