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

QT多线程

使用多线程的好处

假如当前窗口要进行一段非常复杂的逻辑处理,在单线程的情况下,是无法操控界面UI的,点击界面UI没有响应。此时就要用到多线程。

注意:

1、默认的线程在Qt中称之为窗口线程,也叫主线程,负责窗口事件处理或者窗口控件数据的更新
2、子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理
3、主线程和子线程之间如果要进行数据的传递,需要使用Qt中的信号槽机

线程类QThread

常用公共成员函数:

// QThread 类常用 API
// 构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);
// 判断线程中的任务是不是处理完毕了
bool QThread::isFinished() const;
// 判断子线程是不是在执行任务
bool QThread::isRunning() const;// Qt中的线程可以设置优先级
// 得到当前线程的优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);
优先级:QThread::IdlePriority         --> 最低的优先级QThread::LowestPriorityQThread::LowPriorityQThread::NormalPriorityQThread::HighPriorityQThread::HighestPriorityQThread::TimeCriticalPriority --> 最高的优先级QThread::InheritPriority      --> 子线程和其父线程的优先级相同, 默认是这个
// 退出线程, 停止底层的事件循环
// 退出线程的工作函数
void QThread::exit(int returnCode = 0);
// 调用线程退出函数之后, 线程不会马上退出因为当前任务有可能还没有完成, 调回用这个函数是
// 等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);

 信号槽

// 和调用 exit() 效果是一样的
// 调用这个函数之后, 再调用 wait() 函数
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);
// 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数
[slot] void QThread::terminate();// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完毕了
[signal] void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();

静态函数 

// 返回一个指向管理当前执行线程的QThread的指针
[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);	// 单位: 微秒

 任务处理函数

// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();

这个run()是一个虚函数,如果想让创建的子线程执行某个任务,需要写一个子类让其继承QThread,并且在子类中重写父类的run()方法,函数体就是对应的任务处理流程。另外,这个函数是一个受保护的成员函数,不能够在类的外部调用,如果想要让线程执行这个函数中的业务流程,需要通过当前线程对象调用槽函数start()启动子线程,当子线程被启动,这个run()函数也就在线程内部被调用了。

使用方法

方式一

1、需要创建一个线程类的子类,让其继承QT中的线程类 QThread,比如:

class MyThread:public QThread
{......
}

2、重写父类的 run() 方法,在该函数内部编写子线程要处理的具体的业务流程

class MyThread:public QThread
{......protected:void run(){........}
}

3、 在主线程中创建子线程对象,new 一个就可以了

MyThread * subThread = new MyThread;

4、 启动子线程, 调用 start() 方法

subThread->start();

不能在类的外部调用run() 方法启动子线程,在外部调用start()相当于让run()开始运行

当子线程别创建出来之后,父子线程之间的通信可以通过信号槽的方式,注意事项:

1、在Qt中在子线程中不要操作程序中的窗口类型对象, 不允许, 如果操作了程序就挂了
2、只有主线程才能操作程序中的窗口对象, 默认的线程就是主线程, 自己创建的就是子线程

弊端:这种在程序中添加子线程的方式是非常简单的,但是也有弊端,假设要在一个子线程中处理多个任务,所有的处理逻辑都需要写到run()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。

方式二*

1、创建一个新的类,让这个类从QObject派生

class MyWork:public QObject
{.......
}

2、在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑

class MyWork:public QObject
{
public:.......// 函数名自己指定, 叫什么都可以, 参数可以根据实际需求添加void working();
}

3、在主线程中创建一个QThread对象, 这就是子线程的对象

QThread* sub = new QThread;

4、在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象) 

MyWork* work = new MyWork(this);    // error
MyWork* work = new MyWork;          // ok

5、将MyWork对象移动到创建的子线程对象中, 需要调用QObject类提供的moveToThread()方法

// void QObject::moveToThread(QThread *targetThread);
// 如果给work指定了父对象, 这个函数调用就失败了
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub);	// 移动到子线程中工作

6、启动子线程,调用 start(), 这时候线程启动了, 但是移动到线程中的对象并没有工作

7、调用MyWork类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的

优势:使用这种多线程方式,假设有多个不相关的业务流程需要被处理,那么就可以创建多个类似于MyWork的类,将业务流程放多类的公共成员函数中,然后将这个业务类的实例对象移动到对应的子线程中moveToThread()就可以了,这样可以让编写的程序更加灵活,可读性更强,更易于维护。

示例代码

采用方式二

mywork.h

#ifndef MYWORK_H
#define MYWORK_H#include <QObject>class Generate : public QObject
{Q_OBJECT
public:explicit Generate(QObject *parent = nullptr);void doWork(int num);signals:void sendArray(QVector<int> arrayNum);};//冒泡
class BubbleSort : public QObject
{Q_OBJECT
public:BubbleSort(QObject *parent=nullptr);void doWork(QVector<int> arrayNum);
signals:void finished(QVector<int> arrayNum);
};//快排
class QuickSort : public QObject
{Q_OBJECT
public:QuickSort(QObject *parent=nullptr);void doWork(QVector<int> arrayNum);
signals:void finished(QVector<int> arrayNum);
private:void Quick(QVector<int> &arrayNum,int start,int len);
};#endif // MYWORK_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();signals:void starting(int num);private:Ui::Widget *ui;
};#endif // WIDGET_H

main.cpp

#include "widget.h"#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();return a.exec();
}

 mywork.cpp

#include "mywork.h"
#include<QDebug>
#include <QThread>
#include <QElapsedTimer>
Generate::Generate(QObject *parent): QObject(parent)
{}//生成随机数
void Generate::doWork(int num)
{qDebug()<<"生成随机数的线程地址:"<<QThread::currentThread();QElapsedTimer time;time.start();QVector<int> arrayNum;for(int i=0;i<num;i++){arrayNum.push_back(rand()%100000);//qDebug()<<QString::number(arrayNum.at(i));}int milsec=time.elapsed();qDebug()<<"生成随机数用时:"<<milsec<<"毫秒";emit sendArray(arrayNum);
}BubbleSort::BubbleSort(QObject *parent) :QObject(parent)
{}//冒泡排序
void BubbleSort::doWork(QVector<int> arrayNum)
{qDebug()<<"冒泡排序的线程地址:"<<QThread::currentThread();QElapsedTimer time;time.start();for(int i=0;i<arrayNum.size()-1;i++){for(int j=0;j<arrayNum.size()-i-1;j++){if(arrayNum.at(j)>arrayNum.at(j+1)){int temp=arrayNum.at(j);arrayNum[j]=arrayNum.at(j+1);arrayNum[j+1]=temp;}}}int milsec=time.elapsed();qDebug()<<"冒泡排序用时:"<<milsec<<"毫秒";emit finished(arrayNum);
}QuickSort::QuickSort(QObject *parent) :QObject(parent)
{}//快排,调用快排函数进行快速排序
void QuickSort::doWork(QVector<int> arrayNum)
{qDebug()<<"快速排序的线程地址:"<<QThread::currentThread();QElapsedTimer time;time.start();Quick(arrayNum,0,arrayNum.size()-1);int milsec=time.elapsed();qDebug()<<"快速排序用时:"<<milsec<<"毫秒";emit finished(arrayNum);
}
//快速排序函数,start是起始坐标,len是终止坐标
void QuickSort::Quick(QVector<int> &arrayNum, int start, int len)
{if(start<len){int x=start;int y=len;int temp=arrayNum.at(start);while(x<y){while(arrayNum.at(y)>temp && x<y){y--;}if(x<y){arrayNum[x++]=arrayNum.at(y);}while(arrayNum.at(x)<temp && x<y){x++;}if(x<y){arrayNum[y--]=arrayNum.at(x);}}arrayNum[x]=temp;Quick(arrayNum,start,x-1);Quick(arrayNum,x+1,len);}}

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QPushButton>
#include <QThread>
#include "mywork.h"
#include <QMetaType>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//创建线程对象QThread *t1=new QThread;QThread *t2=new QThread;QThread *t3=new QThread;//创建任务对象,对象不能传入this指针Generate *gen=new Generate;BubbleSort *bubble=new BubbleSort;QuickSort *quick=new QuickSort;gen->moveToThread(t1);bubble->moveToThread(t2);quick->moveToThread(t3);//关联信号曹,参数是widget传递生成随机数的数量connect(this,&Widget::starting,gen,&Generate::doWork);//按下开始按钮后,发送strating信号,开始t1线程connect(ui->pushButton,&QPushButton::clicked,this,[=](){emit starting(10000);t1->start();});//QVector<int>类型在Qt的信号槽跨线程通信中,要注册qRegisterMetaType<QVector<int>>("QVector<int>");//关联信号槽,传输生成的随机数组到排序算法中connect(gen,&Generate::sendArray,bubble,&BubbleSort::doWork);connect(gen,&Generate::sendArray,quick,&QuickSort::doWork);//关系信号槽,随机数生成完毕后,显示到窗口,同时启动线程t2,t3connect(gen,&Generate::sendArray,this,[=](QVector<int> arrayNum){for(int i=0;i<arrayNum.size();i++){//qDebug()<<QString::number(arrayNum.at(i));ui->listWidget->addItem(QString::number(arrayNum.at(i)));}t2->start();t3->start();});//冒泡排序完成,则发送信号,显示到窗口connect(bubble,&BubbleSort::finished,this,[=](QVector<int> arrayNum){for(int i=0;i<arrayNum.size();i++){ui->listWidget_2->addItem(QString::number(arrayNum.at(i)));}});//快速排序完成,则发送信号,显示到窗口connect(quick,&QuickSort::finished,this,[=](QVector<int> arrayNum){for(int i=0;i<arrayNum.size();i++){ui->listWidget_3->addItem(QString::number(arrayNum.at(i)));}});//窗口关闭,销毁开辟的线程和对象connect(this,&Widget::destroyed,this,[=](){//关闭线程t1->quit();t1->wait();t1->deleteLater();t2->quit();t2->wait();t2->deleteLater();t3->quit();t3->wait();t3->deleteLater();//销毁对象gen->deleteLater();bubble->deleteLater();quick->deleteLater();//测试子线程是否还在跑qDebug()<<t1->isRunning();qDebug()<<t2->isRunning();qDebug()<<t3->isRunning();});
}Widget::~Widget()
{delete ui;
}

 线程资源释放

方式一

创建线程时,没传入this指针 

 //线程资源要释放,可以在析构函数中释放//但在此,由于new的对象都是局部变量,析构函数无法访问到,所以释放资源时使用信号槽,在窗口销毁的时候释放资源//创建线程对象(管理线程方式,不指定父类对象,关联槽函数,窗口摧毁时关闭线程和销毁对象)QThread *t1=new QThread;QThread *t2=new QThread;QThread *t3=new QThread;

 释放资源

    connect(this,&Widget::destroyed,this,[=](){//关闭线程t1->quit();t1->wait();t1->deleteLater();t2->quit();t2->wait();t2->deleteLater();t3->quit();t3->wait();t3->deleteLater();//销毁对象gen->deleteLater();bubble->deleteLater();quick->deleteLater();//测试子线程是否还在跑qDebug()<<t1->isRunning();qDebug()<<t2->isRunning();qDebug()<<t3->isRunning();});

方式二

创建线程时指定父类对象

   QThread *t1=new QThread(this);
 1.父子对象销毁规则

当父对象被销毁时,Qt会自动递归销毁其所有子对象
此机制仅适用于QObject及其派生类,并且需显式设置父子关系
线程对象t1需满足两个条件才能被自动销毁:
• t1QObject的派生类(如QThread
• t1的父对象被显式设置为this(例如t1 = new QThread(this)

2.线程对象的特殊性

若线程仍在运行(isRunning()==true)时父对象被销毁:
会触发 ‌"QThread: Destroyed while thread is still running"‌ 错误
导致程序强制终止

原因:线程运行时不接受自动销毁指令

3. ‌安全销毁建议
// 正确做法:析构函数中手动管理线程
~MyClass() {if(t1 && t1->isRunning()) {t1->quit();    // 请求终止t1->wait();    // 阻塞等待结束}// 父对象销毁将自动删除t1(因已设置父子关系)
}

关键步骤:
① 调用quit()终止线程任务
② 使用wait()确保线程完全停止
③ 依赖对象树机制自动回收内存

4. ‌未设置父子关系的场景 
t1 = new QThread();  // 未关联this

 this销毁时‌不会‌自动删除t1

必须手动管理内存:delete t1

总结
条件是否自动销毁风险
设置t1 = new QThread(this) + 线程已停止✅ 是
设置父子关系但线程仍在运行❌ 否程序崩溃
未设置父子关系❌ 否内存泄漏

 最佳实践‌:始终在析构函数中显式调用quit()wait(),即使设置了父子关系

http://www.lqws.cn/news/508663.html

相关文章:

  • Git 子模块 (Submodule) 完全使用指南
  • 烟花爆竹生产企业库房存储安全风险预警系统
  • 【Pandas】pandas DataFrame update
  • 【Docker基础】Docker容器管理:docker stop详解
  • Vue.js:渐进式框架赋能现代Web开发
  • 蓝桥杯嵌入式学习(cubemxkeil5)
  • word中如何快速打出上标?
  • 20250624java面试总结
  • 第九节 CSS工程化-预处理技术对比
  • 大白话蓝牙中的RPC:Remote Procedure Call远程过程调用
  • 壁挂马桶品牌推荐:我的“瑞尔特瑞家HX5”沉浸式体验报告健康与洁净的硬核科技
  • 从设备自动化到智能管控:MES如何赋能牛奶饮料行业高效生产?
  • 2025年渗透测试面试题总结-2025年HW(护网面试) 10(题目+回答)
  • Flask(四) 模板渲染render_template
  • 用Rust写平衡三进制加法器
  • 调试HDMI音频能8通道播放声音
  • 开疆智能CCLinkIE转ModbusTCP网关连接川崎机器人配置案例
  • linux grep的一些坑
  • GelSight Mini视触觉传感器开发资源升级:触觉3D点云+ROS2助力机器人科研与医疗等应用
  • Flutter动画开发:从基础到高级实战
  • Luckysheet Excel xlsx 导入导出互相转换
  • css color 十六进制颜色透明度
  • Harmony状态管理 @Local和@Param
  • 机器人编程语言要如何选择?
  • 十二(1)、Webshell工具哥斯拉
  • Spring Boot 切面编程(AOP)详细教程
  • mongoose解析http字段值
  • 梳理React中的fiber架构
  • React Hooks详解
  • Kafka的消费消息是如何传递的?