Qt线程池的实现
- 线程池的意义,避免线程频繁的创建销毁,实现线程复用
- 线程池的三个主要组成部分:任务队列、工作线程若干,管理者线程
- Qt中实现线程用到的主要类:
QThreadPool
QRunnable
线程池的三个主要组成部分
-
任务队列
存储需要处理的任务,有线程从任务队列中获取任务来进行处理。
Qt中有将任务添加到任务队列的API接口QThreadPool::start(QRunnable *runnable,int priority = 0);
已经处理了的任务会从任务队列中删除
生产者线程,调用上述的API往任务队列中添加任务,这个线程就是生产者线程,也就是线程池的使用者 -
工作队列若干个
线程池维护着若干个线程,这些就是工作线程,工作线程可能在处理任务当中,这时的该线程就称为活跃线程,不在处理任务中的就称为空闲线程。工作线程的作用就是不停的读取任务队列,从中取出任务来进行处理。工作线程就相当于任务队列中消费者的角色,而调用API往任务队列中添加任务的生产者线程就是任务队列的生产者了。
如果任务队列为空时,工作线程将会被阻塞。阻塞之后工作队列又有了新的任务,现成将有生产者也就是上述调用API往任务队列中添加队列的线程解除工作线程的阻塞,工作线开始工作
-
管理者线程
不会处理任务,管理者线程的任务是周期性的检查任务队列中的任务个数和处于活跃状态的工作线程的个数。当任务过多的时候可以适当的创建一些线程,当任务过少的时候可以适当的销毁一些线程。
Qt使用线程池常用到的类
QRunnable
QRunnable
在Qt中使用线程池首先要创建任务,添加到线程池中的每一个类都是QRunnable
类型的,看QTreadPool::start()
的参数类型就知道,所以我们创建的任务类多要继承QRunnable
类。然后重写run
方法,里面是处理任务的逻辑。然后将这个任务对象添加到任务队列中。这样这个任务就可以被线程中的工作线程给处理掉了。
QRunnable
中的常用接口(API)
[pure virtual ]void QRunnable::run()
在继承本类的任务类中必须重写的纯虚函数,里面是处理任务的逻辑流程[pure virtual]void QRunnable::setAutoDelete(bool autoDelete)
参数设置为true这该任务会在处理完成后该任务对象会被自动销毁,设置为false则在任务处理完成后该任务对象需要程序员手动进行销毁。bool QRunnable::aotoDelete()const
返回当前任务对象时候设置了自动析构,也就是第二个接口是否设置为了true。
简单的任务类示例
Class MyWork : public QRunnable,public QObject
{
Q_OBJECT
explicit MyWork(QWidget* parent = nullptr){ setAutoDelete(true);}
void run()override{ //处理任务的逻辑}
};
QTreadPool
QTreadPool
这是Qt提供的管理线程池的类,用于管理和复用线程,该类管理了一组QTreads,里面还维护了一个任务队列,也就是说连任务队列都不用自己创建了,直接使用该类提供的接口往里面添加就行了,QTreadPool管理和回收各个QTread对象,以减少使用线程的程序中的线程的创建成本。每一个Qt应用程序都有一个全局的QTreadPool对象,可以通过globalInstance()
来访问。也可以单独创建一个QTReadPool对象使用。
主要的接口函数
void setMaxThreadCount(int maxThreadCount);
设置线程池中的最大线程数int maxTreadCount()const;
获取线程池中的最大线程数void start(QRunnable* runnable)
往线程池的任务队列中添加任务,任务是一个QRunnable对象bool tryStart(QRunnable* runnable)
尝试往线程池任务队列中,不是强制的添加,如果有空闲的工作线程,就会添加成功且立即被执行,如果没有空闲的线程就会添加失败。bool tryTake(QRunnable* runnable)
尝试将某个任务移除,如果该任务已经开始执行了就移除失败返回false。int activeTreadCount()const
线程池中活跃的线程数也就是正在处理任务的线程数void clear()
清楚任务队类中的所有未开始执行的任务,如果开始执行了就不能被清除了static QTreadPool *QTreadPool::globalInstance()
返回一个全局的线程池对象,每个Qt应用程序都会有一个,可以通过这函数直接访问,是个单例。
一般情况下,我们不需要在Qt程序中创建线程池对象,直接使用Qt为每个应用程序提供的线程池全局对象即可。得到线程池对象之后,调用start()方法就可以将一个任务添加到线程池中,这个任务就可以被线程池内部的线程池处理掉了,使用线程池比自己创建线程的这种多种多线程方式更加简单和易于维护。
Qt中使用线程池现在发现的一个作用就是可以先显示界面的同时去做其他的事情
简单的Qt使用多线程示例
mywork.h
#ifndef MYWORK_H
#define MYWORK_H
#include <QRunnable>
#include <QDebug>
class MyWork : public QRunnable
{
public:
MyWork();
void run();
};
#endif // MYWORK_H
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QThreadPool>
#include "mywork.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
MyWork *task1;
};
#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"
MyWork::MyWork()
{
setAutoDelete(true);
}
void MyWork::run()
{
qDebug()<<"hello world!!!";
}
widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
task1 = new MyWork;
QThreadPool::globalInstance()->start(task1);
}
Widget::~Widget()
{
delete ui;
}