Qt 多线程

Qt 多线程

Qt 提供两种多线程编程范式:基于事件循环的 QThread 模型,以及更高层封装的 QtConcurrent API。

一、Qt 多线程与信号槽机制

Qt 提供基于事件驱动的多线程编程模型,其核心在于 QThread 类与信号槽(Signal & Slot)通信机制。该机制专门设计用于处理跨线程通信,能够自动管理线程间的数据同步与函数调用。

1.1 核心原理:跨线程信号槽连接

在进行跨线程通信时,Qt 的信号槽机制扮演了关键角色。槽函数总是由接收该信号的对象所在的线程来执行。

  • 自动线程安全:当信号槽连接被正确建立后,开发者无需手动加锁,系统会保证槽函数在接收者对象所属的线程上下文中被安全调用。
  • 执行模式 :支持两种连接方式:
    • Qt::DirectConnection:槽函数在发送信号者的线程中立即执行。
    • Qt::QueuedConnection:槽函数被放入接收者线程的事件队列中,由该线程的事件循环调度执行。
1.1.1 默认跨线程连接方式

对于在不同线程中的对象之间建立的信号槽连接,Qt 的默认行为是 Qt::QueuedConnection。这意味着:

  1. 发送信号的线程会将信号及其参数封装为一个事件(QMetaCallEvent)。
  2. 该事件被放入接收者对象所属线程的事件队列
  3. 当接收者线程运行到事件循环(QThread::exec())时,会取出并处理该事件,从而执行对应的槽函数。

关键前提 :接收者线程必须运行事件循环,QueuedConnection 才能正常工作。

1.2 QThread 的正确使用范式

QThread 本身是 Qt 提供的线程管理类,核心原则是: QThread 管理的是线程的上下文,而非业务逻辑本身。

1.3 代码示例:Worker 类定义

cpp 复制代码
#include "Worker.h"

Worker::Worker(QObject* p) :QObject(p)
{

}
void Worker::Register(const std::function<void(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 类实现

cpp 复制代码
#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::function<void(const Info& info)> m_func;
public:
void Register(const std::function<void(const Info& info)>& func);
signals:
void SendProcess(int i);
void SendProcessMsg(const Info& info);
void finished();
};

1.5 代码示例:主窗口中的线程启动与管理

cpp 复制代码
#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 跨线程访问 UI:QMetaObject::invokeMethod

采用 QMetaObject::invokeMethod 方法可以实现跨线程访问 UI。需要注意 ,跨线程访问 UI 时,QMetaObject::invokeMethod 中的连接参数必须设置为 Qt::QueuedConnection

QMetaObject::invokeMethod 是 Qt 元对象系统 提供的跨线程调用方法,仅用于 QObject 对象 ,其核心作用是:让一段代码在「目标对象所属的线程」中执行

若想让代码在 主线程(用于更新 UI) 中执行,第一个参数必须是主线程中的 QObject 对象(例如窗口 this、UI 控件)。

cpp 复制代码
// 目标:在主线程安全地更新UI
// 第一个参数传递主线程对象(this / mp_lb)→ 预执行的代码块将在主线程中执行
QMetaObject::invokeMethod(this, [=]() {
mp_lb->setText("主线程安全更新UI");
}, Qt::QueuedConnection);

invokeMethod 调用的对象必须满足以下条件:

cpp 复制代码
// 自定义类型
struct Info {

QString msg;
Info(const QString& _msg) :msg(_msg) {};
Info() {};
Info(const Info& _info)
{
this->msg = _info.msg;
}
};

// 主线程初始化时注册该元类型
qRegisterMetaType<Info>("Info");

使用 invokeMethod 调用既有方法时,该方法需要:

cpp 复制代码
Q_INVOKABLE void UpdateLbInfo(const Info& info);

2.4 代码示例:QtConcurrent 结合 invokeMethod

cpp 复制代码
#include "QThreadTest.h"

QThreadTest::QThreadTest(QWidget *parent)
: QMainWindow(parent)
{
qRegisterMetaType<Info>("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() 方法中(旧式用法)。推荐的做法是:

    1. 将业务逻辑封装在一个独立的 Worker 对象(继承自 QObject)中。
    2. 将该 Worker 对象通过 moveToThread() 方法移动到 QThread 对象所代表的新线程中。
    3. 通过信号槽机制,从主线程(或其他线程)向 Worker 对象发送指令,或接收其发出的进度、完成等信号。
    • 自动管理线程池,无需编写 moveToThreadconnectdeleteLater 等底层代码。
    • 代码量极少,上手门槛低。
    • 短期、简单的耗时操作,例如计算密集型任务、文件读取、网络请求等。
    • 不需要长期持有线程的场景。
    1. 必须是 QObject 的派生类
    2. 类声明中必须包含 Q_OBJECT
    3. 如需传递自定义类型参数,必须先行注册该类型的元类型。
    • 使用 Q_INVOKABLE 宏声明的普通成员函数;或者
    • public slot 槽函数。
相关推荐
Yu_Lijing2 小时前
基于C++的《Head First设计模式》笔记——MVC模式
c++·笔记·设计模式
派带星-2 小时前
基于 C++ 的第三方 SDK 封装实践(ASR + 短信服务)
c++·ide·macos
xiaoye-duck2 小时前
《算法题讲解指南:动态规划算法--简单多状态dp问题》--17.买卖股票的最佳时机III,18.买卖股票的最佳时机IV
c++·算法·动态规划
Yupureki2 小时前
《Linux网络编程》2.Socket编程(UDP/TCP)
linux·服务器·c语言·网络·c++·tcp/ip·udp
AIminminHu3 小时前
OpenGL渲染与几何内核那点事-项目实践理论补充(二-1-(1):当你的CAD学会“想象”:图形技术与AI融合的三个层次)
c++·人工智能·几何·cad·几何内核·cad开发
ada0_ada13 小时前
Qt的Widgets项目
开发语言·qt
Dovis(誓平步青云)3 小时前
《QT学习第一篇:QT的概述与安装、信号与槽》
开发语言·qt·学习·功能详解
2301_789015623 小时前
C++11新增特性:列表初始化&左值引用&右值引用&万能引用&移动构造&移动赋值&引用折叠&完美转发
c语言·开发语言·c++·c++11
迷海3 小时前
力扣原题《打家劫舍》递归版动态规划,纯手搓,已验证,未优化
c++·leetcode·动态规划