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