一、多线程与线程池的应用目的[1][4]
(一)多线程
一个进程内多个线程并发执行的情况就叫多线程,每一个线程是一个独立的执行流。多线程是一种编程模型,它与处理器无关,与设计机制有关。
需要多线程的原因包括:
1. 并行计算。充分利用多处理器内核,提升整体吞吐量,加快执行速度;
2. 后台任务处理。将后台线程和主线程分离,在特定场景下它是不可或缺的,如响应式用户界面、实时系统等。
(二)线程池
线程池主要作用是避免创建过多的线程而引发内存溢出问题,因为创建线程还是比较消耗内存的。
线程池的主要优势:
1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行;
3. 提高线程的客观理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
二、认识QThread、QThreadPool和QFuture[1-3][9]
(一)QThread
在程序运行过程中,一个QThread对象管理一个线程的控制。QThread对象通过成员函数run()执行,默认情况下,run()调用exec()开启一个事件循环并且该事件循环在线程内部运行。
(二)QThreadPool
一个QThreadPool对象管理和回收单个QThread对象,以降低程序中的线程创建成本。每个Qt应用程序都有一个全局QThreadPool对象,可以通过调用global allinstance()来访问该对象。使用线程池中的其中一个线程,需要实现一个继承QRunnable的类并且重载其中的虚函数run(),然后创建一个实现类的对象实例并通过QThreadPool::start()启动。
(三)QFuture
QFuture允许多线程同步,协调一个或多个线程的结果在某个时间点汇总并按预期步骤向前推进。其中,QFuture提供了与运行计算进行交互的方法,提供了基于QFutureWatcher信号与槽的方式与运行的任务进行交互等。
多线程同步(串行):
多线程同步是指,协调多个线程对共享数据的访问,避免出现数据不一致的情况或协调各个事件的发生顺序,使多线程在某个点交汇并按预期步骤往前推进(例如某线程需要等另一个线程完成某项工作才能开展该线程的下一步工作)。
识别什么地方需要同步是编写多线程程序的难点,只有准确识别需要保护的数据、需要同步的点,再配合系统或编程语言提供的合适的同步机制,才能编写安全高效的多线程程序。
多线程异步(并行):
多线程异步是指,允许同一时间处理多个事件。程序调用一个耗时较长的功能时,它并不会阻塞程序的执行流程,程序会继续往下执行,当该功能执行完毕时,程序能够获得执行完毕的消息或能够访问到执行的结果(如果有返回值或需要返回值时)。
多线程异步具有提高程序的响应能力和处理能力,特别适用于IO密集型任务。
三、基于Qt的独立线程执行方法与代码Demo[5-6][8]
基于Qt C++的软件开发中,多线程之间的通信是通过信号与槽机制实现的。需要注意的是,只有主线程才能操作程序中的窗口对象,默认的线程就是主线程(窗口对象的实例化会默认生成一个线程),自己创建的就是子线程。
(一)基于实现QThread子类进行独立线程创建运行的方法
1. 伴随着事件循环实现
在QThread子类的run()重载函数中调用QThread::exec()可以开启一个本地线程事件循环;通过调用QThread::quit()或QThread::exit()将会终止事件循环;通常为了保证线程的正常结束,会调用QThread::wait()阻塞其他线程以等待该线程中事件处理完后再结束线程;通常基于QThread::start()运行QThread子类线程实例。相关具体内容可详见参考资料[8]。
1.1 无需实现重载QThread::run()函数的QThread子类创建的独立线程执行方法
详见参考资料的使用方法2,Qt中多线程的使用 | 爱编程的大丙 (subingwen.cn)。实际上,该方法利用了QThread::run()默认实现QThread::exec()调用的特性。
1.2 实现启动事件循环的重载QThread::run()函数的QThread子类创建的独立线程执行方法
详见参考资料[6]的1:47-2:39视频段,人为主动调用QThread::exec()。
2. 无需借助事件循环实现
定义一个QThread子类并重载QThread::run()函数(重载时不调用QThread::exec()开启本地线程事件循环),将全部需求代码写入QThread::run()函数中。然后,在主线程中创建一个实例并通过QThread::start()启动该线程即可。该方法需要在主线程中自主管理线程的创建和回收等。
// mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
class MyThread:public QThread
{
Q_OBJECT //元对象系统元对象声明
public:
explicit MyThread(QObject *parent = nullptr); // 默认构造函数
protected:
// 重载的run()方法需要通过QThread::start()在类外部调用
void run() override;
};
#endif // MYTHREAD_H
// mythread.cpp
#include "mythread.h"
MyThread::MyThread(QObject *parent):QThread(parent){}
void MyThread::run()
{
// 此处写入子线程全部逻辑代码
}
// 主线程调用方法
MyThread *subThread = new MyThread; // 动态创建子线程
subThread->start();
subThread->quit(); // 子线程终止管理
subThread->wait();
或
MyThread subThread; // 静态创建子线程
subThread.start();
subThread.quit(); // 子线程终止管理
subThread.wait();
(二)基于QThreadPool进行独立线程管理的方法
详见参考资料的使用方法,Qt中线程池的使用 | 爱编程的大丙 (subingwen.cn)。
具体项目代码可详见系列博客/*2*/的绑定资源。
(三)基于QFuture进行独立线程创建运行的方法
QFuture<void>专门用于不包含任何获取结果返回函数的线程。任何 QFuture<T>也可以分配或复制到 QFuture<void>中,如果只需要状态或进度信息,而不是实际的结果数据,QFuture<void>很有用。具体使用方法可详见参考资料[8]。
/*
QFuture使用简单Demo-以管理QFuture<void>线程为例
多线程同步需要引入QMutex进行数据控制管理(Mutex.lock()、Mutex.unlock())
*/
// 不包含获取结果返回的线程声明
QFuture<void> Concurrent_Thread;
// 在主线程调用设计的槽函数
Concurrent_Thread = QtConcurrent::run(this, &MainWindow::Concurrent_Thread_Function);
// 用于恢复被暂停的线程函数
Concurrent_Thread.resume();
// 用于暂停和取消的线程函数
Concurrent_Thread.pause();
Concurrent_Thread.cancel();
四、系列博客回顾
多线程间基于信号与槽机制进行信号传递的方法与代码示例详见系列博客/*1*/,基于QFuture实现多线程同步/异步运行的具体方法解释与项目代码文件详见系列博客/*2*/。
参考资料:
[1] 基本功 | 一文讲清多线程和多线程同步 - 美团技术团队 (meituan.com)
[2] 从小白到高手,你需要理解同步与异步 - 知乎 (zhihu.com)
[3] The Difference Between Asynchronous and Multi-Threading | Baeldung on Computer Science
[4] 面试必问的线程池-深入了解线程池的作用及原理_线程工厂什么作用-CSDN博客
[5] Qt 教程 | 爱编程的大丙 (subingwen.cn)
[6] https://www.youtube.com/watch?v=SncJ3D-fO7g(QThread with an event loop)
[7] https://www.youtube.com/watch?v=lTJ-QkC_Sxw(Signals and Slots across Threads)
[8] Assistant 5.15.2 (MSVC 2019 64-bit)(Qt使用文档助手)
[9] https://www.youtube.com/watch?v=W3ec-_7VPeM(Comparison of Qt and STL Multithreading Classes)
系列博客:
/*1*/ Qt的信号槽机制学习一-CSDN博客
/*2*/ 基于Qt的多线程同步和异步运行实验Demo_qt多线程demo-CSDN博客