目录
一、多线程是什么
二、为什么要使用多线程进行开发
1、提高处理效率
2、软件运行更加流畅
3、根据优先级依次处理
三、Qt多线程入门
QThread类,类成员及类成员函数介绍
公有变量
公有函数
公有槽函数
信号
静态公有成员
保护成员函数
静态保护成员
多线程开发方式
1、继承QObject类,使用moveToThread方法
2、继承QThread类,自定义线程类及类方法
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。
如今具有多核多线程的CPU处理器已经不罕见,例如2021年Inter推出的11700k处理器具有8个核心,支持16个线程同时并行运作,协调地完成任务的处理。在此基础上,为了更加注重程序的实时性能和计算处理的能力,在程序设计时一般采用多线程等并行处理的方法进行优化。多线程就是把操作系统中的这种并发执行机制原理运用在一个程序中,把一个程序划分为若干个子任务,多个子任务并发执行,每一个任务就是一个线程。例如,对一个文件在同一时间段内进行读取和写入;在不阻塞主线程UI显示的情况下完成后台的数据处理;餐馆运营时,多人够买套菜时套餐数量的实时维护等。
了解了多线程是什么和一些简单的使用场景后,下面介绍一些有关多线程编程的概念。
1、进程:进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序,数据集合和进程控制块三部分组成。
2、线程:在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。
3、并发执行:一次可以执行多个任务,但不是同时执行,比如烧水、泡茶、喝茶。
4、并行执行: 一次可以同时执行多个任务,比如左手画圆的同事,右手画方。
5、线程竞争: 同一时间内,多个线程访问共享或能够同时拥有的公共资源,且至少有一个线程需要更改该资源的内部数据时,此时就发生了线程之间的竞争。
6、线程阻塞: 在发生线程竞争的基础上,某个线程获取到了公共资源,同时在对该资源操作的时间段内加锁,只允许它自己拥有对该资源访问、修改的权利,这个时间段内的除它之外的线程都会被阻塞。
7、线程死锁: 两个或两个以上的线程,互相持有对方的资源,且同时加锁不释放,此时就发生了线程的死锁。例如:线程一获取到锁一,等锁二释放后继续运行;线程二获取到锁二,等锁一释放后继续运行,这样就容易发生死锁。
8、线程同步: 即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。
假设你的工程需要读取数据库的表,数据库表有100张,普通的单线程执行读取100张表中的数据花费的时间为100s(假设每张表读取耗时1s)。如果此时启动10个线程,每个线程依次分别读取10张表的数据,且同时执行,那么读取100张表的数据花费的时间仅需要10s,缩短了90%的时间,极大地提升了数据处理的效率。
假设你的工程是一个股票分析软件,左侧窗口需要实时请求当前价格并在图表中显示,右侧窗口支持股票的买入卖出。若使用一个线程进行处理,当线程在更新左侧图标时,我们就无法使用右侧窗口的买入卖出功能,甚至无法响应其他UI接口,同理,我们买入卖出的时候也可能阻塞UI。如果股票分析软件的UI是主线程,左侧窗口的试试价格显示开一个线程,右侧的买入卖出开一个线程,此时在进行操作时就不存在互相影响的情况,也不会导致UI界面卡死。
任务都分轻重缓急,如果使用单线程进行处理,无法根据轻重缓急来进行动态调整。使用多线程开发就可以设置线程的优先度,这样CPU就可以根据预设优先度来完成任务的执行。
Qt中多线程的头文件引用为:#include
(或 #include "qthread.h") 官方文档地址:QThread Class
QThread 公有继承于 QObject 类。
常量 | 值 | 描述 |
---|---|---|
QThread::IdlePriority | 0 | 当且仅当没有其他线程运行的时候进行安排执行 |
QThread::LowestPriority | 1 | 安排执行的频率低于LowPriority |
QThread::LowPriority | 2 | 安排执行的频率低于NormalPriority |
QThread::NormalPriority | 3 | 操作系统默认的执行优先级 |
QThread::HighPriority | 4 | 安排执行的频率高于NormalPriority |
QThread::HighestPriority | 5 | 安排执行的频率高于HighPriority |
QThread::TimeCriticalPriority | 6 | 尽可能频繁的安排执行 |
QThread::InheritPriority | 7 | 使用创建线程时的优先级。默认设置 |
构造一个 QThread 类来管理一个新的线程。parent 拥有这个线程的所有权。在调用 start() 函数之前,线程不会开始执行。
销毁 QThread 类。注意,删除 QThread 对象并不会停止它内部线程的执行。删除一个正在执行的进程可能会导致崩溃,在删除 QThread 对象前需要等待 finished() 的信号。
告诉线程的事件循环以返回码 returnCode 退出。
如果线程已经完成, 返回 true;否则返回 false。
如果事件循环正在进程,返回 true;否则返回 false。事件循环定义为调用 exec() 到退出 exit() 所执行的代码段。
返回正在运行线程的优先级。如果线程未运行,则返回 InheritPriority。
设置正在运行线程的优先级。如果线程未运行,则不执行任何操作返回。priority 参数可以是除了 InheritPriorty 之外的任何 QThread::Priority 枚举值。
将线程的最大堆栈设置为 stackSize。如果 stackSize 大于0,则最大堆栈大小设置为 stackSize 字节,否则最大堆栈大小由操作系统自动确定。
注意:大多数操作系统对线程堆栈大小设置了最小和最大限制。如果堆栈大小超出这些限制,线程将无法启动。
如果使用 setStackSize 设置了最大堆栈,返回该数值;否则返回 0。
阻塞等待,直到满足以下任一条件:
1、QThread 对象关联的线程已执行完毕。函数返回 true。
2、时间已经过去 time 毫秒。如果时间值为 ULONG_MAX(默认值),那么 wait() 函 数将永远等待下去,不会超时。如果等待超时,函数将返回 false。
告诉线程的事件循环以返回码 0 退出。相当于调用 QThread::exit(0)。
调用 run() 函数开始线程执行。操作系统会根据 priority 参数调度线程。如果线程在调用前已经运行,则这个函数什么也不做。
终止线程的执行。取决于操作系统的调度策略,线程也可能不会立即被终止,策略通过监听 terminated() 信号或使用 QThread::wait() 来进行。
当线程终止时,所有在等待的线程都会被唤醒。
注意:这个函数很危险,不推荐使用。有可能造成死锁或内存泄漏。
进入事件循环并等待 exit() 被调用。该函数由 run() 内部调用。
线程的起点。调用 start() 后,新创建的线程将调用这个函数。默认实现知识调用 exec()。
由官方文档可知,使用Qt进行多线程开发的方式主要有两种:
class Worker : public QObject
{
Q_OBJECT
QThread workerThread;
public slots:
void doWork(const QString ¶meter) {
// ...
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString)));
connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
class WorkerThread : public QThread
{
Q_OBJECT
void run() {
QString result;
/* expensive or blocking operation */
emit resultReady(result);
}
signals:
void resultReady(const QString &s);
};
void MyObject::startWorkInAThread()
{
WorkerThread *workerThread = new WorkerThread(this);
connect(workerThread, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
workerThread->start();
}