QT-多线程、线程池的使用

在进行桌面应用程序开发的时候, 假设应用程序在某些情况下需要处理比较复杂的逻辑,如果只有一个线程去处理,就会导致窗口卡顿,无法处理用户的相关操作。这种情况下就需要使用多线程,其中一个线程处理窗口事件,其他线程进行逻辑运算,多个线程各司其职,不仅可以提高用户体验,还可以提升程序的执行效率。

在 qt 中使用了多线程,有些事项是需要额外注意的:

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

一、QT多线程实现方法(1)- 基于QThread

  • 创建subThread线程类,继承QThread类
  • 重写父类的run()方法,即具体要执行的任务
  • 主线程中创建子线程对象,调用start()方法启动子线程

二、QT多线程实现方法(2)- 基于QObject

  • 创建工作类,继承QObject类
  • 添加一个公共的成员函数,函数体就是子线程要执行的业务逻辑(后面把这个任务函数作为槽函数使用)
  • 主线程中创建子线程对象
  • 创建工作类对象(注意不能给他指定父对象)
  • 将工作类对象移动到创建的子线程的对象中,需要调用QObject的moveToThread()方法
  • 启动子线程,调用start()方法,但此时线程虽然启动了,但移动到线程中的对象没有工作
  • 调用工作类对象的工作函数,让这个函数执行业务

第二种方法更灵活,一个工作类可以有多个工作函数,多个工作对象也可以移动到同一个子线程中,可以人为控制指定线程执行指定任务。在第一种方法中,如果run函数需要传入的数据,可以通过信号槽机制+成员变量的形式实现接收,工作函数run函数是不能直接传入参数的;而在第二种方法中,工作函数是可以有参数的,可以通过信号槽机制直接传入数据,而不用再定义成员变量。

第一种方法简单,只需要书写线程类,重写run函数,然后在主线程中创建子线程对象,通过这个子线程对象调用start方法工作。弊端就是,如果需求、任务足够多,写入到run函数中,判定就复杂,run函数也越臃肿,因此在任务简单、少的情况下使用第一种方法

需要注意的一个点,使用connect(发出者,信号,接收者,处理函数)时,

c 复制代码
connect(gen,&Generate::sendArray,bubble,&BubbleSort::working);
connect(gen,&Generate::sendArray,quick,&QuickSort::working);
//上下两种是不一样的,下面变成了在主线程里处理,因为bubble对象移动到了子线程里,
//所以bubble->working()也是在子线程下执行的
connect(gen,&Generate::sendArray,this,[=](QVector<int> list){
	bubble->working();
	quick->working();
});
    

三、线程资源释放

创建子线程对象时指定父对象,这样Qt就会自动回收这些资源,按照QT的内存回收机制,第一个是指定的父对象得是QObejct类的子类或者间接子类,第二个就是创建对象时必须指定父对象,父对象析构的时候会根据对象树先析构子对象

第二种方式在当前窗口类的析构函数中释放掉这些资源,就是手动析构。当然写的程序有时候析构函数是访问不到哪些子线程对象的,如果不想把这些子线程对象设置为窗口类的成员变量,那我们还可以通过信号-槽的机制来回收这些资源,窗口关闭时发出destroed的信号,这里利用信号槽,收到这个信号时释放掉子线程对象、任务对象的资源

c 复制代码
    connect(this, &MainWindow::destroyed, this, [=]()
    {
        t1->quit();
        t1->wait();
        t1->deleteLater();
        
        t2->quit();
        t2->wait();
        t2->deleteLater();
        
        t3->quit();
        t3->wait();
        t3->deleteLater();
        
        gen->deleteLater();  // delete t1;
        bubble->deleteLater();
        quick->deleteLater();
    });

四、线程池

Qt提供了QThreadPool线程池类,然后有一列的API

c 复制代码
// 在每个Qt应用程序中都有一个全局的线程池对象, 通过这个函数直接访问这个对象
static QThreadPool * QThreadPool::globalInstance();
//调用start方法,把任务封装成QRunnable 对象,扔给线程池,线程池下的空闲线程就会帮我们处理对应任务
//QRunnable * runnable 任务的类型,QRunnable 主要是run函数和设置释放自动析构的API
//priority 优先级
void QThreadPool::start(QRunnable * runnable, int priority = 0);
  • 先创建一个要添加到线程池中的任务类,继承QObject(可以使用信号槽机制)、QRunnable
  • 重写run 方法(继承Qthread的run是protected,而QRunnable 的run是public),里面就是执行任务,另外还需要在构造函数里设置自动析构
  • 主函数中创建任务对象,调用QThreadPool::globalInstance()->start(task)执行任务;

五、基于线程池执行多任务

基于线程池可以将方法(1)的多线程修改为线程池实现多任务

参考博客

相关推荐
用户805533698035 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner5 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
小bo波10 天前
使用Thread子类创建线程 VS 使用Runnable接口创建线程的区别
java·多线程·thread·并发编程·runnable
Quz10 天前
QML Hello World 入门示例
qt
xcyxiner13 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner14 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner14 天前
DicomViewer (添加模型类)3
qt
xcyxiner15 天前
DicomViewer (目录调整) 2
qt
xcyxiner15 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能17 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构