Qt 的锁和条件变量,和 C++ 标准库本质上解决的是同一类线程同步问题;区别主要在 API 风格、和 Qt 事件机制/对象体系的融合程度,以及可移植性和使用习惯上。
可以直接把它们一一对应起来:
| Qt | C++ 标准库 |
|---|---|
QMutex |
std::mutex |
QRecursiveMutex |
std::recursive_mutex |
QReadWriteLock |
std::shared_mutex |
QMutexLocker |
std::lock_guard / std::unique_lock |
QWaitCondition |
std::condition_variable |
QSemaphore |
std::counting_semaphore(C++20) |
1. QMutex 和 std::mutex
基本用法几乎一样。
Qt:
1 | QMutex mutex; |
C++:
1 | std::mutex mutex; |
都不推荐手动 lock/unlock,因为中途异常或 return 容易忘记解锁。
Qt 一般配:
1 | QMutex mutex; |
作用域结束自动解锁。
对应 C++:
1 | std::mutex mutex; |
所以:
1 | QMutexLocker |
不过 QMutexLocker 还支持主动:
1 | locker.unlock(); |
所以它功能上也有点像:
1 | std::unique_lock |
2. QWaitCondition 和 std::condition_variable
这两个概念几乎完全一样。
典型场景:
消费者线程发现队列为空,不要 while 死循环,而是睡眠等待;生产者放入数据后唤醒消费者。
Qt:
1 | QMutex mutex; |
消费者:
1 | void consume() |
生产者:
1 | void produce(int data) |
对应 C++:
1 | std::mutex mutex; |
消费者:
1 | void consume() |
生产者:
1 | void produce(int data) |
两者机制完全一致:
1 | 线程拿到锁 |
这里有个非常重要的面试点:
条件变量不是用来“保护数据”的,锁才是;条件变量只是负责让线程高效等待某个条件成立。
3. Qt 条件变量一个明显区别:API 更 Qt 风格
QWaitCondition:
1 | condition.wait(&mutex); |
直接把 QMutex* 传进去。
而标准库:
1 | std::unique_lock<std::mutex> lock(mutex); |
为什么标准库一定要求 unique_lock?
因为 condition_variable::wait() 内部需要反复:
1 | unlock mutex |
因此它需要一个“可解锁又可重新加锁”的 lock 对象。
std::lock_guard 不行,因为:
1 | std::lock_guard |
只能构造时上锁、析构时解锁,不能主动 unlock()。
所以标准条件变量通常搭配:
1 | std::unique_lock |
Qt 把这一层包装掉了:
1 | QWaitCondition::wait(QMutex*) |
内部直接处理 mutex。
4. Qt 和 C++ 锁能不能混着用?
可以,但通常:
一个模块里尽量统一。
比如纯业务库:
1 | core/ |
更推荐:
1 | std::mutex |
因为它们:
- 不依赖 Qt
- 标准 C++
- 更方便以后脱离 Qt
- 服务端和客户端代码都能复用
而 Qt 客户端强相关代码:
1 | UI |
用:
1 | QMutex |
也完全合理。
例如你写:
1 | C++ Agent SDK |
我肯定优先推荐:
1 | std::mutex |
而不是让 SDK 依赖 Qt。
如果是:
1 | Qt 工业客户端 |
本来项目已经深度依赖 Qt,QMutex 没什么问题。
5. Qt 还有一个很常见的 QReadWriteLock
这个对应:
1 | std::shared_mutex |
适合:
读多写少
例如某个全局配置:
1 | QReadWriteLock lock; |
读:
1 | lock.lockForRead(); |
多个线程可以同时读:
1 | Thread A ─┐ |
但是写的时候:
1 | lock.lockForWrite(); |
必须独占:
1 | Thread A 读 ─┐ |
RAII 写法:
1 | QReadLocker locker(&lock); |
和:
1 | QWriteLocker locker(&lock); |
对应 C++:
1 | std::shared_lock<std::shared_mutex> |
和:
1 | std::unique_lock<std::shared_mutex> |
6. Qt 多线程其实经常“不需要锁”
这个反而是 Qt 和普通 C++ 多线程思维一个很重要的区别。
普通 C++ 可能写:
1 | Thread A |
而 Qt 更推荐:
1 | Thread A |
也就是:
尽量通过消息传递数据,而不是让两个线程直接共享状态。
比如 Worker 做完推理:
1 | emit resultReady(result); |
然后 UI:
1 | connect(worker, &Worker::resultReady, |
此时如果 result 是值传递:
1 | Worker Thread |
很多情况下根本不需要 mutex。
这实际上是一种很好的并发设计思想:
1 | 共享内存 + mutex |
转变为:
1 | 消息传递 + event queue |
所以 Qt 项目里如果你看到到处都是 mutex
反而应该警惕:
是不是线程之间共享了太多可变状态?
7. 什么时候 Qt 里确实需要锁?
例如多个线程真的共享一个资源。
假设:
1 | class ResultCache { |
Camera Thread:
1 | 写 cache |
Inference Thread:
1 | 读 cache |
Storage Thread:
1 | 读/删 cache |
那就需要锁。
又比如 TensorRT。
假设你只有一个:
1 | IExecutionContext* context; |
多个工作线程:
1 | Thread 1 ─┐ |
而这个 context 不是线程安全的。
那就:
1 | QMutex mutex; |
这里锁保护的是:
非线程安全共享资源。
8. 条件变量最经典的实际用途
比如做相机 + 推理:
1 | Camera Thread |
错误方案:
1 | while (running) { |
这是:
busy waiting,忙等。
没有图片的时候 CPU 也一直循环,可能直接吃掉一个 CPU 核。
正确方案:
1 | Camera Thread |
没有帧:
1 | Inference Thread |
新帧来了:
1 | Camera |
这就是条件变量最典型的价值。
Qt 的
QMutex、QWaitCondition和 C++ 标准库里的std::mutex、std::condition_variable本质上解决的是同样的同步问题,底层最终也会依赖操作系统提供的线程同步原语。主要区别是 API 和框架集成。比如 Qt 使用QMutexLocker做 RAII,QWaitCondition::wait()可以直接接受QMutex;标准库通常用std::unique_lock配合condition_variable。在 Qt 客户端里,我一般不会一上来就使用锁。如果线程之间可以通过 signal-slot 和 queued connection 传递数据,我会优先使用消息传递,减少共享状态。只有多个线程确实需要访问共享资源,比如任务队列、缓存或者非线程安全的推理 context 时,才使用 mutex;如果还需要等待“队列非空”这类条件,则配合条件变量,避免 busy waiting。