当前位置: 首页 > news >正文

第六章 QT基础:7、Qt中多线程的使用


在进行桌面应用程序开发时,假设应用程序需要处理比较复杂的逻辑,如果只有一个线程去处理,就会导致窗口卡顿,无法处理用户的相关操作。

这种情况下,需要使用多线程:

  • 主线程处理窗口事件和控件更新
  • 子线程进行后台逻辑处理
  • 多线程协作提升用户体验和执行效率

多线程开发注意事项

  • Qt默认的线程叫做窗口线程(主线程),负责处理窗口控件和界面更新。
  • 子线程负责后台的业务逻辑,子线程中不能直接操作窗口控件
  • 主线程和子线程之间的数据通信,使用信号与槽机制完成。

1. 线程类 QThread

Qt 提供了 QThread 类来创建子线程,有两种常用的子线程使用方式。


1.1 常用成员函数

// 构造函数,创建一个线程对象
QThread::QThread(QObject *parent = Q_NULLPTR);// 查询线程是否结束(任务执行完毕)
bool QThread::isFinished() const;// 查询线程是否正在运行
bool QThread::isRunning() const;// 设置和获取线程优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);// 退出线程(退出事件循环)
void QThread::exit(int returnCode = 0);// 等待线程结束(通常配合exit()使用)
bool QThread::wait(unsigned long time = ULONG_MAX);

1.2 信号与槽

// 请求线程正常退出(发送退出指令)
[slot] void QThread::quit();// 启动线程,内部自动调用run()
[slot] void QThread::start(Priority priority = InheritPriority);// 强制终止线程(不推荐,可能导致资源泄漏)
[slot] void QThread::terminate();// 线程执行完成时发出的信号
[signal] void QThread::finished();// 线程开始运行时发出的信号
[signal] void QThread::started();

1.3 静态函数

// 获取当前执行的线程指针
[static] QThread* QThread::currentThread();// 获取当前系统的理想线程数(通常等于CPU核心数)
[static] int QThread::idealThreadCount();// 线程休眠函数
[static] void QThread::msleep(unsigned long msecs); // 毫秒级休眠
[static] void QThread::sleep(unsigned long secs);   // 秒级休眠
[static] void QThread::usleep(unsigned long usecs); // 微秒级休眠

1.4 任务处理函数

// 子线程逻辑的入口函数,需要重写
[virtual protected] void QThread::run();
  • 注意 run() 是 虚函数,只能通过子类重写。
  • 不要手动调用 run(),应通过 start() 启动线程,让Qt自动调用 run()

2. 使用方式一:继承QThread重写run()


2.1 操作步骤

[!important]

  1. 创建一个子类继承自 QThread
  2. 重写 run() 函数,写入业务逻辑
  3. 在主线程中 new 子线程对象
  4. 调用 start() 启动子线程

2.2 示例代码(含详细注释)

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QThread>// 自定义线程类,继承自QThread
class MyThread : public QThread
{Q_OBJECT
public:explicit MyThread(QObject *parent = nullptr); // 构造函数protected:void run() override; // 重写run(),处理子线程任务//子线程的`run()`是由系统自动调用的,不应该让别人直接调用,否则容易破坏线程机制signals:void curNumber(int num); // 自定义信号,用于传递当前数字
};#endif // MYTHREAD_H

mythread.cpp

#include "mythread.h"
#include <QDebug>MyThread::MyThread(QObject *parent) : QThread(parent) {}
//第一个MyThread::是定义的是属于 `MyThread` 这个类的东西
//第二个MyThread构造函数的名字,在C++里构造函数名字必须和类名一模一样void MyThread::run()
{qDebug() << "当前线程对象地址:" << QThread::currentThread();int num = 0;while (1){emit curNumber(num++); // 每次加一并发出信号if (num == 10000000)   // 数到一定数量退出break;QThread::usleep(1);    // 每次循环稍作休眠,减少CPU占用}qDebug() << "run()执行完毕,子线程退出...";
}

mainwindow.cpp

#include "mainwindow.h"    // 引入主窗口头文件
#include "ui_mainwindow.h"  // 引入主窗口UI界面头文件
#include "mythread.h"       // 引入自定义线程类头文件
#include <QDebug>           // 引入Qt调试输出模块// 主窗口的构造函数
// 使用初始化列表,调用父类QMainWindow的构造函数MainWindow,并传入parent对象指针
//parent的主要作用是建立父子对象关系,方便自动内存管理!
MainWindow::MainWindow(QWidget *parent)//子类名::构造函数名(参数): QMainWindow(parent),         // 1. 调用父类QMainWindow的构造函数,传入parentui(new Ui::MainWindow)        // 2. 初始化成员变量ui,new出一个新的Ui::MainWindow对象
{ui->setupUi(this);              // 3. 在构造体内部,初始化界面// 输出当前线程对象的地址// QThread::currentThread() 返回当前正在运行的线程指针qDebug() << "主线程对象地址:  " << QThread::currentThread();// 创建自定义子线程对象// 注意:这里没有设置父对象,生命周期需要自己管理,或合理搭配QObject父子机制MyThread* subThread = new MyThread;// 连接子线程发出的curNumber信号到主线程的槽函数(用lambda表达式)// 作用:收到子线程发的数字信号后,更新UI界面label上的数值connect(subThread, &MyThread::curNumber, this, [=](int num){ui->label->setNum(num);  // 把收到的数字设置到界面上的label控件});// 连接“开始”按钮的点击信号到槽函数(用lambda表达式)// 作用:点击按钮时启动子线程connect(ui->startBtn, &QPushButton::clicked, this, [=](){subThread->start();  // 启动子线程,内部自动调用子线程的run()方法});
}// 主窗口析构函数
// 作用:释放UI资源
MainWindow::~MainWindow()
{delete ui;
}

3. 使用方式二:QObject+moveToThread()


3.1 操作步骤

[!important]

  1. 创建一个普通类继承自 QObject
  2. 定义公共函数作为业务处理函数(如 working)
  3. 创建 QThread 对象
  4. 创建工作对象(不要设置父对象
  5. 使用 moveToThread() 把工作对象移到子线程
  6. 启动子线程,绑定信号槽调用工作函数

3.2 示例代码(含详细注释)

mywork.h

#ifndef MYWORK_H
#define MYWORK_H#include <QObject>// 自定义工作对象
class MyWork : public QObject
{Q_OBJECT
public:explicit MyWork(QObject *parent = nullptr);void working(); // 工作逻辑函数signals:void curNumber(int num); // 自定义信号传递当前数字
};#endif // MYWORK_H

mywork.cpp

#include "mywork.h"    // 引入自定义工作类头文件
#include <QDebug>      // 引入Qt调试打印模块
#include <QThread>     // 引入Qt线程模块,使用currentThread()和usleep()// 构造函数实现
// 使用初始化列表方式,调用父类QObject的构造函数,传入parent指针
MyWork::MyWork(QObject *parent) : QObject(parent) {}// 自定义的工作函数,具体执行子线程任务
void MyWork::working()
{// 打印当前执行working()的线程对象指针// QThread::currentThread()是静态函数,返回当前运行线程的指针qDebug() << "当前线程对象地址:" << QThread::currentThread();int num = 0; // 定义计数变量,从0开始// 无限循环while (1){//在子线程里处理数据后,及时把处理结果发送(通知)给主线程。emit curNumber(num++); // 每次计数后,发射curNumber信号,把当前num值发出去// 如果计数到10000000,跳出循环,结束工作if (num == 10000000)break;QThread::usleep(1);    // 休眠1微秒,避免CPU占用过高(防止死循环卡死)}// 循环结束,任务完成,打印提示qDebug() << "working()执行完毕,子线程退出...";
}

mainwindow.cpp

#include "mainwindow.h"       // 引入主窗口头文件
#include "ui_mainwindow.h"     // 引入自动生成的UI界面头文件
#include <QThread>             // 引入Qt线程模块
#include "mywork.h"            // 引入自定义工作类头文件
#include <QDebug>              // 引入Qt调试打印模块// 主窗口构造函数
// 使用初始化列表调用父类QMainWindow构造函数,并创建UI对象
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this); // 初始化界面控件// 打印当前线程对象地址(主线程)qDebug() << "主线程对象地址:" << QThread::currentThread();// 创建子线程对象(QThread对象)QThread* sub = new QThread;// 创建工作对象(自定义的MyWork类实例)// 注意:不能设置parent,否则moveToThread会失败!MyWork* work = new MyWork;// 将工作对象移动到子线程中执行// 注意:只能移动继承自QObject的对象work->moveToThread(sub);// 启动子线程// 此时QThread内部开启一个空事件循环(event loop),为工作对象提供子线程环境sub->start();// 连接按钮点击信号到工作对象的working槽函数// 作用:点击按钮后,让work开始执行working()方法connect(ui->startBtn, &QPushButton::clicked, work, &MyWork::working);//connect(发送者, 发送者的信号, 接收者, 接收者的槽函数);//&MyWork::working指向 `MyWork` 类的成员函数 `working()`//- Qt在不同线程之间发信号自动切换线程(通过队列连接QueueConnection)。  //- 子线程不能直接操作界面,但发信号给主线程处理是安全的!// 连接工作对象发出的curNumber信号到主线程的槽// 作用:收到子线程发回的数字后,在界面label上显示connect(work, &MyWork::curNumber, this, [=](int num){ui->label->setNum(num);});
}// 主窗口析构函数
// 释放UI资源
MainWindow::~MainWindow()
{delete ui;
}
connect(ui->startBtn, &QPushButton::clicked, work, &MyWork::working);

![[Pasted image 20250428134905.png]]

connect(work, &MyWork::curNumber, this, [=](int num){ui->label->setNum(num);
});

![[Pasted image 20250428134855.png]]


4. 小结

使用方式优点缺点
继承QThread重写run简单直观,适合小任务run()过于臃肿时维护困难
QObject+moveToThread灵活,适合多个任务类写法复杂,要求掌握对象迁移

5. 版权信息

  • 作者: 苏丙榅
  • 链接: https://subingwen.cn/qt/thread/#3-2-示例代码
  • 来源: 爱编程的大丙
  • 版权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

✅ 示例场景:多线程文件拷贝(防止界面卡顿)


copyworker.h

#ifndef COPYWORKER_H
#define COPYWORKER_H#include <QObject>class CopyWorker : public QObject
{Q_OBJECT
public:explicit CopyWorker(QObject *parent = nullptr);void setFilePaths(const QString &src, const QString &dest);public slots:void startCopy(); // 复制操作入口(槽函数)signals:void progress(int percent);  // 进度更新信号void finished();             // 完成信号void error(QString msg);     // 错误信息
private:QString m_srcFilePath;QString m_destFilePath;
};#endif // COPYWORKER_H

copyworker.cpp

#include "copyworker.h"
#include <QFile>
#include <QFileInfo>
#include <QThread>CopyWorker::CopyWorker(QObject *parent): QObject(parent)
{}void CopyWorker::setFilePaths(const QString &src, const QString &dest)
{m_srcFilePath = src;m_destFilePath = dest;
}void CopyWorker::startCopy()
{QFile srcFile(m_srcFilePath);QFile destFile(m_destFilePath);if (!srcFile.open(QIODevice::ReadOnly)) {emit error("源文件无法打开!");return;}if (!destFile.open(QIODevice::WriteOnly)) {emit error("目标文件无法创建!");return;}QFileInfo info(srcFile);qint64 totalSize = info.size();qint64 copied = 0;const qint64 bufferSize = 1024 * 1024; // 1MB缓冲while (!srcFile.atEnd()) {QByteArray data = srcFile.read(bufferSize);destFile.write(data);copied += data.size();int percent = static_cast<int>((copied * 100.0) / totalSize);emit progress(percent);  // 发射当前进度QThread::msleep(50);     // 模拟耗时}srcFile.close();destFile.close();emit finished();  // 复制完成信号
}

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "copyworker.h"#include <QThread>
#include <QFileDialog>
#include <QMessageBox>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);// 创建线程和工作对象QThread* thread = new QThread(this);CopyWorker* worker = new CopyWorker;worker->moveToThread(thread);connect(ui->btnSelectFile, &QPushButton::clicked, this, [=] {QString file = QFileDialog::getOpenFileName(this, "选择源文件");ui->lineEditSource->setText(file);});connect(ui->btnSelectTarget, &QPushButton::clicked, this, [=] {QString file = QFileDialog::getSaveFileName(this, "选择目标路径");ui->lineEditDest->setText(file);});connect(ui->btnStartCopy, &QPushButton::clicked, this, [=] {QString src = ui->lineEditSource->text();QString dest = ui->lineEditDest->text();if (src.isEmpty() || dest.isEmpty()) {QMessageBox::warning(this, "提示", "请填写完整路径");return;}worker->setFilePaths(src, dest);emit startWork();  // 发出信号让子线程开始执行});// 绑定信号到槽函数(使用队列连接,线程安全)connect(this, &MainWindow::startWork, worker, &CopyWorker::startCopy);connect(worker, &CopyWorker::progress, this, [=](int p){ui->progressBar->setValue(p);});connect(worker, &CopyWorker::finished, this, [=]{QMessageBox::information(this, "完成", "文件复制完成!");});connect(worker, &CopyWorker::error, this, [=](const QString &msg){QMessageBox::critical(this, "错误", msg);});thread->start(); // 启动线程
}MainWindow::~MainWindow()
{delete ui;
}

mainwindow.h 信号声明

signals:void startWork(); // 让子线程开始执行的信号

UI界面组件要求

  • lineEditSource:显示源路径
  • lineEditDest:显示目标路径
  • btnSelectFile:选择源文件按钮
  • btnSelectTarget:选择目标按钮
  • btnStartCopy:启动复制按钮
  • progressBar:进度条(范围0~100)

✅ 技术要点总结

技术点说明
QThread + QObject实现真正的工作线程架构
moveToThread()将工作对象迁移到线程中
信号槽机制主线程与子线程通信(线程安全)
UI线程与逻辑分离不阻塞界面,提升用户体验
线程安全UI更新使用信号更新界面,避免子线程直接操作控件
http://www.xdnf.cn/news/213463.html

相关文章:

  • Knife4j 接口文档添加登录验证流程分析
  • 天能资管(SkyAi):全球布局,领航资管新纪元
  • 单片机-89C51部分:9、串行口通讯
  • TTL、RS-232 和 RS-485 串行通信电平标准区别解析
  • 【C语言练习】010. 理解函数参数的传递方式
  • 深度解析Qwen3:性能实测对标Gemini 2.5 Pro?开源大模型新标杆的部署挑战与机遇
  • 牛客周赛 Round 91
  • k8s 学习记录 (六)_Pod 污点和容忍性详解
  • 日常开发小Tips:后端返回带颜色的字段给前端
  • 数据结构:实验7.3Huffman树与Huffman编码
  • 【18】爬虫神器 Pyppeteer 的使用
  • 信息科技伦理与道德3-4:面临挑战
  • 宾馆一次性拖鞋很重要,扬州卓韵酒店用品详细介绍其材质与卫生标准
  • 论文导读 - 基于特征融合的电子鼻多任务深度学习模型研究
  • 【无基础】小白解决Docker pull时报错:https://registry-1.docker.io/v2/
  • Html 2
  • verl - 火山引擎大语言模型强化学习训练库
  • Wi-SUN与LoRa和NB-IoT通信技术的对比
  • AI+零售:智能推荐、无人店与供应链管理的未来
  • 基于STM32、HAL库的DS28E15P安全验证及加密芯片驱动程序设计
  • Kafka 消息可靠性深度解析:大流量与小流量场景下的设计哲学
  • [逆向工程]如何理解小端序?逆向工程中的字节序陷阱与实战解析
  • 搜索引擎中的检索模型(布尔模型、向量空间模型、概率模型、语言模型)
  • 贵族运动项目有哪些·棒球1号位
  • CSR社会责任报告是什么?CSR社会责任报告定义
  • C++ 如何计算两个gps 的距离
  • 基于 ARM 的自动跟拍云台设计
  • 【无标题】好用的远程链接插件
  • 水安题库:水利水电安全员ABC精选练习题
  • 阿里巴巴Qwen3发布:登顶全球开源模型之巅,混合推理模式重新定义AI效率