Qt 中的多线程 01:重写 run 函数
Qt 中的多线程主要是为了防止复杂耗时的操作阻塞主线程,导致界面卡死的问题。可以通过继承 QThread
类后,重写 run()
函数来实现。
一、 定义继承自 QThread
的类
定义一个类继承自 QThread
,并重写虚函数 run()
,将耗时的操作放在 run()
函数中,然后在主线程中来通过调用该类的 start()
函数,从而实现启动子线程执行 run()
函数中的内容。
1、 定义继承自 QThread
类 WorkThread
,作为子线程
WorkThread.h
内容如下:
#pragma once
#include <QThread>
/// <summary>
/// 通过继承 QThread类,并重写 run 函数来实现多线程操作,
/// 将复杂的操作放在run函数里面,run()函数相当于运行在一个新的线程中。
/// 需要公有继承 QThread 类,否则后面无法调用重载的 run 函数
/// </summary>
class WorkThread: public QThread
{
Q_OBJECT
public:
WorkThread();
~WorkThread();
/**
* @brief 在该函数中实现耗时阻塞主线程的操作,该函数中的操作将会在子线程中执行
*/
void run() override;
signals:
/**
* 子线程无法直接操作UI,因此只能通过信号槽的机制来向主线程发送信号,
* 主线程接受到该信号后,进行更新UI内容,从而实现间接操作UI.
*/
void SendMsg(QString msg);
private:
int num1;
};
WorkThread.cpp
内容如下:
#include "WorkThread.h"
WorkThread::WorkThread()
{
}
WorkThread::~WorkThread()
{
// 使用quit()或者exit()使得子线程能够退出消息循环,而不会一直阻塞在子线程中
this->quit();
// 等待子线程退出后,回收子线程的资源
this->wait();
}
void WorkThread::run()
{
num1++ ;
int curId = (int)QThread::currentThreadId();
emit SendMsg(QString("子线程id = %1").arg(curId));
emit SendMsg("Start do something ...");
QThread::sleep(10);
QString resStr = "Finish complex operation";
emit SendMsg(resStr);
// 子线程中的消息循环函数,如果没有该函数,则子线程执行完后会立即退出
// this->exec();
}
2、在主线程中调用子线程
QThreadTest.h
内容如下:
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_QThreadTest.h"
#include "WorkThread.h"
class QThreadTest : public QMainWindow
{
Q_OBJECT
public:
QThreadTest(QWidget *parent = Q_NULLPTR);
~QThreadTest();
private slots:
/**
* 直接在主线程中进行耗时操作,会直接阻塞主UI线程,导致程序卡死.
*/
void OnBtn01Clicked();
/**
* @brief 将创建一个线程来执行复杂的操作,避免阻塞UI线程。
* 该类继承自 QThread,将复杂的操作写在 run()中,
* run()函数中的内容才会在子线程中执行。
*/
void OnBtn02Clicked();
/**
* @brief 由于子线程不能直接操作UI,所以该函数用于将接受到的子线程内容显示在UI上
* @param [in] msg
*/
void GetResult(QString msg);
private:
Ui::QThreadTestClass ui;
WorkThread* workThread; // 子线程的对象指针
};
QThreadTest.cpp
内容如下:
#include "QThreadTest.h"
#include <QPushButton>
QThreadTest::QThreadTest(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
// 在构造函数中初始化子线程的对象指针
workThread = new WorkThread();
// 子线程只能通过信号槽来间接的修改UI的内容
connect(workThread, &WorkThread::SendMsg, this, &QThreadTest::GetResult);
// 子线程结束之后释放其所占用的资源
connect(workThread, &WorkThread::finished, workThread, &WorkThread::deleteLater);
connect(ui.btn01, &QPushButton::clicked, this, &QThreadTest::OnBtn01Clicked);
connect(ui.btn02, &QPushButton::clicked, this, &QThreadTest::OnBtn02Clicked);
}
QThreadTest::~QThreadTest()
{
}
void QThreadTest::OnBtn01Clicked()
{
ui.textBrowser_01->append("Start do something ...");
QThread::sleep(5);
ui.textBrowser_01->append("Finishing this operation.");
}
void QThreadTest::OnBtn02Clicked()
{
ui.textBrowser_02->append(QString("主线程id = %1").arg((int)QThread::currentThreadId()));
// 启动子线程,开始执行 run 函数中的耗时操作,避免阻塞主线程。
workThread->start();
}
void QThreadTest::GetResult(QString msg)
{
ui.textBrowser_02->append("workThread: " + msg);
}
效果如下图所示:

点击 Btn01
后直接导致程序卡住,无法响应其它的操作。点击Btn02
后会在子线程中执行耗时操作,此时主 UI 线程仍然可以响应用户的操作,不会阻塞主线程。
【注意】
- 如果在主线程的析构函数中,即
~QThreadTest()
中来释放子线程的资源,必须要确保子线程workThread
的资源还未退出释放,即run()
函数中有exec()
消息循环来阻止子线程释放资源。否则子线程执行完后会自动退出并释放资源,此时在主线程的析构函数中再次释放子线程的资源则会导致重复释放的异常。 QThread
的派生类WorkThread
中,除了 run() 函数是在新的子线程中执行的,其它的函数都是在实例化WorkThread
类对象的线程中执行的(即主线程中),如WorkThread
类的构造函数,析构函数都是在主线程中执行的。且WorkThread
类的对象也属于主线程。WorkThread
类的成员变量 num1 既可以被 run() 函数所在的子线程访问,也可以被主线程所访问。因此在访问修改该变量的时候需要确保是否安全,来避免数据的不一致性,必要的时候需要加锁进行保护。
分类:
Qt
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!