本文结构如下:概述
任务的统一封装形式:QRunnable为什么要统一封装形式?
如何使用 QRunnable?
如何使用信号槽通信?
线程池的本质是什么?线程池中的两种数据之一:任务
线程池中的两种数据之一:线程
线程池如何处理这两种数据?
如何使用 QThreadPool线程池相关
线程相关
任务相关
附:所有函数
1. 概述
一般的多线程任务大多是避免主线程阻塞(界面卡死),开销线程的次数少。现在有一个光伏监控系统用于采集光伏板的发电功率,每次接收完网络数据包就会进行数据库的写操作。为了不占用主 GUI 线程,这一过程都在新线程里完成。但是成百上千的光伏板时时刻刻都会传送数据过来,如果每一次的执行都完整的创建线程-执行-销毁线程,可见这对于资源的消耗是何等之高。
根据《Qt 多线程编程之敲开 QThread 类的大门》中的「3.1 开多少个线程比较合适?」所讲“频繁的切换线程会使性能降低”,尤其是资源密集型操作,如需要 CPU 进行大量的运算任务。为了提高效率,在不增加资源(如 CPU 核数)的情况下,如何利用现有资源成了唯一的解决思路。线程池就是在这样的背景下诞生的。
2. 任务的统一封装形式:QRunnable
2.1 为什么要统一封装形式?
从直观的角度来看,线程池里有若干个线程,我们只要将执行的任务“扔”到这个“池子”里就可以了。在 Qt 中,所有需要线程池的任务都用 QRunnable 统一封装起来,为什么要这样做而不是类似 QObject::moveToThread() 的方式呢?其实这样做只是为了方便管理而已。上百个任务如果都是统一的类,那就可以用一个数组来管理了。
除此以外,能用到线程池的任务,基本上功能单一且并不需要和其他线程进行信号槽的通信。所以基于这样的场景,Qt 没有将 QRunnable 设计成 QObject 的子类,也是最大限度地精简“任务”负担。
2.2 如何使用 QRunnable?
QRunnable()
virtual ~QRunnable()
bool autoDelete() const
void setAutoDelete(bool autoDelete)
virtual void run() = 0
使用 QRunnable 非常简单,继承后重写 run() 函数,然后“扔”给线程池即可。成员函数也就 autoDelete() 这么一种,用于设置对象的所有权。默认设置为 true,线程结束后会自动删除该对象,也就是说扔到线程里就不用管何时去删除的问题了。简单的使用示例如下代码所示:
class HelloWorldTask : public QRunnable
{
void run() override
{
qDebug() << "Hello world";
}
};
HelloWorldTask *task = new HelloWorldTask();
QThreadPool::globalInstance->start(task);
2.3 如何使用信号槽通信?
因为 QRunnable 不是 QObject 的子类,因此不能使用信号槽这种元对象系统特性。如果使用场景需要信号槽通信,解决办法就是采用 QObject、QRunnable 双继承形式,例如下列代码:
class WorkRunnable : public QObject, public QRunnable
{
Q_OBJ