Qt 多线程

张开发
2026/4/6 19:16:20 15 分钟阅读

分享文章

Qt 多线程
Qt 多线程Qt 提供两种多线程编程范式基于事件循环的QThread模型以及更高层封装的QtConcurrentAPI。一、Qt 多线程与信号槽机制Qt 提供基于事件驱动的多线程编程模型其核心在于QThread类与信号槽Signal Slot通信机制。该机制专门设计用于处理跨线程通信能够自动管理线程间的数据同步与函数调用。1.1 核心原理跨线程信号槽连接在进行跨线程通信时Qt 的信号槽机制扮演了关键角色。槽函数总是由接收该信号的对象所在的线程来执行。自动线程安全当信号槽连接被正确建立后开发者无需手动加锁系统会保证槽函数在接收者对象所属的线程上下文中被安全调用。执行模式支持两种连接方式Qt::DirectConnection槽函数在发送信号者的线程中立即执行。Qt::QueuedConnection槽函数被放入接收者线程的事件队列中由该线程的事件循环调度执行。1.1.1 默认跨线程连接方式对于在不同线程中的对象之间建立的信号槽连接Qt 的默认行为是Qt::QueuedConnection。这意味着发送信号的线程会将信号及其参数封装为一个事件QMetaCallEvent。该事件被放入接收者对象所属线程的事件队列。当接收者线程运行到事件循环QThread::exec()时会取出并处理该事件从而执行对应的槽函数。关键前提接收者线程必须运行事件循环QueuedConnection才能正常工作。1.2 QThread 的正确使用范式QThread本身是 Qt 提供的线程管理类核心原则是QThread管理的是线程的上下文而非业务逻辑本身。1.3 代码示例Worker 类定义#include Worker.h Worker::Worker(QObject* p) :QObject(p) { } void Worker::Register(const std::functionvoid(const Info info) func) { m_func func; } void Worker::doWork() { for (int i 1; i 10; i) { QThread::sleep(1); emit SendProcess((i / 10.0 * 100)); if(m_func) m_func(Info(std::to_string((i / 10.0 * 100)).c_str())); emit SendProcessMsg(Info(std::to_string((i / 10.0 * 100)).c_str())); } emit finished(); }1.4 代码示例Worker 类实现#pragma once #include qobject.h #include qthread.h struct Info { QString msg; Info(const QString _msg) :msg(_msg) {}; Info() {}; Info(const Info _info) { this-msg _info.msg; } }; class Worker:public QObject { Q_OBJECT public: explicit Worker(QObject* p nullptr); public slots: void doWork(); private: std::functionvoid(const Info info) m_func; public: void Register(const std::functionvoid(const Info info) func); signals: void SendProcess(int i); void SendProcessMsg(const Info info); void finished(); };1.5 代码示例主窗口中的线程启动与管理#include QThreadTest.h QThreadTest::QThreadTest(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); QHBoxLayout* hbLayout new QHBoxLayout(ui.centralWidget); mp_lb new QLabel(QString::fromLocal8Bit(开始计时), ui.centralWidget); hbLayout-addWidget(mp_lb); ui.centralWidget-setLayout(hbLayout); QThread* pThread new QThread; Worker* pWorker new Worker;//这里需要注意下不能设置父节点是this否则Worker无法moveToThread到子线程上 pWorker-moveToThread(pThread); connect(pThread, QThread::started, pWorker, Worker::doWork); connect(pWorker, Worker::SendProcess, [](int i) { mp_lb-setText(QString::fromLocal8Bit(当前进度 std::to_string(i) %)); }); connect(pWorker,Worker::finished, pThread, QThread::quit); connect(pWorker, Worker::finished, pWorker, QThread::deleteLater); connect(pWorker, Worker::finished, pThread, QThread::deleteLater); pThread-start(); } QThreadTest::~QThreadTest() {}二、Qt Concurrent 进阶 API无需手动管理线程Qt Concurrent 是 Qt 提供的高级封装开发者无需手动创建QThread通常只需一行代码即可实现跨线程操作适合处理简单的耗时任务。2.1 核心优势2.2 适用场景2.3 跨线程访问 UIQMetaObject::invokeMethod采用QMetaObject::invokeMethod方法可以实现跨线程访问 UI。需要注意跨线程访问 UI 时QMetaObject::invokeMethod中的连接参数必须设置为Qt::QueuedConnection。QMetaObject::invokeMethod是 Qt元对象系统提供的跨线程调用方法仅用于 QObject 对象其核心作用是让一段代码在「目标对象所属的线程」中执行。若想让代码在主线程用于更新 UI中执行第一个参数必须是主线程中的 QObject 对象例如窗口 this、UI 控件。// 目标在主线程安全地更新UI // 第一个参数传递主线程对象this / mp_lb→ 预执行的代码块将在主线程中执行 QMetaObject::invokeMethod(this, []() { mp_lb-setText(主线程安全更新UI); }, Qt::QueuedConnection);被invokeMethod调用的对象必须满足以下条件// 自定义类型 struct Info { QString msg; Info(const QString _msg) :msg(_msg) {}; Info() {}; Info(const Info _info) { this-msg _info.msg; } }; // 主线程初始化时注册该元类型 qRegisterMetaTypeInfo(Info);使用invokeMethod调用既有方法时该方法需要Q_INVOKABLE void UpdateLbInfo(const Info info);2.4 代码示例QtConcurrent 结合 invokeMethod#include QThreadTest.h QThreadTest::QThreadTest(QWidget *parent) : QMainWindow(parent) { qRegisterMetaTypeInfo(Info); ui.setupUi(this); QHBoxLayout* hbLayout new QHBoxLayout(ui.centralWidget); mp_lb new QLabel(QString::fromLocal8Bit(开始计时), ui.centralWidget); hbLayout-addWidget(mp_lb); ui.centralWidget-setLayout(hbLayout); QtConcurrent::run([]() { Worker worker; connect(worker, Worker::SendProcessMsg, [](const Info info) { QMetaObject::invokeMethod(this, QThreadTest::UpdateLbInfo, Qt::QueuedConnection, info); }); worker.doWork(); }); } QThreadTest::~QThreadTest() {} void QThreadTest::UpdateLbInfo(const Info info) { mp_lb-setText(QString::fromLocal8Bit(当前进度 info.msg.toStdString() %)); }发射信号的线程将信号事件放入接收者线程的事件队列中。业务逻辑的封装开发者不应将业务逻辑直接写在QThread子类的run()方法中旧式用法。推荐的做法是将业务逻辑封装在一个独立的Worker对象继承自QObject中。将该Worker对象通过moveToThread()方法移动到QThread对象所代表的新线程中。通过信号槽机制从主线程或其他线程向Worker对象发送指令或接收其发出的进度、完成等信号。自动管理线程池无需编写moveToThread、connect、deleteLater等底层代码。代码量极少上手门槛低。短期、简单的耗时操作例如计算密集型任务、文件读取、网络请求等。不需要长期持有线程的场景。必须是QObject 的派生类。类声明中必须包含Q_OBJECT宏。如需传递自定义类型参数必须先行注册该类型的元类型。使用Q_INVOKABLE宏声明的普通成员函数或者是public slot槽函数。

更多文章