QtConcurrent::run场景下跨线程发射Qt信号的线程安全性问题
问题:QtConcurrent::run中调用主线程QObject信号的回调是否线程安全?
我正在使用QtConcurrent::run(因其他原因无法使用QtConcurrent的其他内置进度报告API)在非GUI主线程中运行操作,同时需要该操作向GUI线程通知进度。为此我创建了一个接受进度回调的操作函数,该回调会调用位于主线程的QObject的信号。以下是完整的可运行示例代码:
#include <QCoreApplication> #include <QObject> #include <QThread> #include <QtConcurrent/QtConcurrent> namespace Operations { template<typename Callback> void longOperation(Callback progressCallback) { qint64 sum = 0; for(int i = 0; i < 100; ++i){ QThread::msleep(400); sum += i; progressCallback(i/100.0); } } } class Emitter : public QObject { Q_OBJECT public: Q_INVOKABLE void doSomething() { auto progressCallback = [&](qreal p){ emit progress(p); }; auto lambda = [progressCallback](){ Operations::longOperation(progressCallback); }; QtConcurrent::run(lambda); } signals: void progress(qreal); }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Emitter emitter; QObject::connect(&emitter, &Emitter::progress, [](qreal progress){ qDebug() << "Progress" << progress; }); emitter.doSomething(); return a.exec(); } #include "main.moc"
我的问题是:如上定义的progressCallback是否线程安全?该回调显然会在非GUI线程中触发,相当于直接在QObject上调用emitter.progress()。
回答
先给结论:这个progressCallback的写法是线程安全的,但核心原因不是直接调用信号本身安全,而是Qt的信号槽机制帮你处理了线程切换。
具体说明:
- 你的
Emitter对象在主线程创建,线程归属是主线程。当你在非主线程里调用emit progress(p)时,Qt会自动检查信号发送线程和接收者(Emitter)所在线程是否一致。 - 默认的信号槽关联方式是
Qt::AutoConnection,一旦检测到线程不一致,Qt会把信号打包成事件投递到主线程的事件队列,由主线程的事件循环触发槽函数。这相当于间接完成了线程间的安全通信,不会出现跨线程直接操作对象的风险。 - 要是手动把关联方式改成
Qt::DirectConnection,槽函数会直接在发信号的非主线程执行,此时如果槽内操作GUI元素就会有线程安全问题,但你的示例里只是用qDebug输出,不会有问题。
另外提个代码里的潜在风险:你用[&]捕获创建的progressCallback,会隐式捕获this(因为在Emitter的成员函数中),虽然示例里Emitter对象直到程序结束才销毁,不会有悬空引用问题,但如果以后Emitter可能提前被销毁,这个写法就会引发错误,这点需要注意。
内容的提问来源于stack exchange,提问作者qt_overflow
相关产品推荐
相关产品推荐

