QT知识整合--多线程
一、线程概念
由于本期是讲QT中多线程的使用,计算机中线程的概念不可能在本期里面写的太过详细,如果想要了解的更加清楚的话自己去找计算机操作系统的书或者看一下其他关于线程的帖子。
线程是计算机中一个概念,它是程序执行的最小单元。在线程之前还有一个叫“进程”的概念,一个线程是在进程内执行的一个独立单元,可以并发的执行程序。
而多线程也就是一个进程可以有多个线程,多个线程同时执行多个程序来达到操作系统的高性能特性。
我们举两个例子来更好的理解线程和进程。
就拿我们公司来举例,我们公司是在一个办公大厦里面的小房间,这栋楼里面拥有许多的公司,每个公司都拥有自己的业务,有外贸、科技、金融等。那么这整栋楼我们可以理解为计算机本身,每个房间就是一个进程,而我们每个人就是一个线程单体。再细说一点,如何理解我为一个线程,就拿我们公司来说,包括我在内的多人组成一个团队,并且现在我们正在开发一个软件,如果项目过大我们就要分工开发不同的模块,每个人都负责自己的开发部分就行了,我们将每个人比作一个线程就合理了。虽然一个人可以开发完,但是需要的时间成本很高,所以多线程概念运用到此处就大大提高。
再来举一个计算机内的例子,我们在计算机中打开一个软件就是一个进程。
当你运行一个软件时,操作系统会为该软件创建一个进程,并且在这个进程内部可能会包含多个线程来执行不同的人。如果不是用多线程技术的话,整个软件运行起来只会有一个功能会运行。假如我们打开的是一个QQ程序,我们在一个群里面下载一个资料,如果我们移动这个窗口下载就会停止,松开鼠标这个下载任务又会开始。这不难看出多线程在计算机软件中的作用了。
由上面的介绍总结,多线程的作用体现在分工合作去完成一件事,这样的好处可以做到分出去的工作会做的更精确,整体的工作会更加高效且有组织。就如计算机中,操作系统的启动是一个非常多的任务集合体组成,所包含的任务“数以万计”。其中包含内核任务、系统服务和用户任务等。而内核中又包括进程调度、内存管理、文件系统、设备管理等。系统任务就有网络服务、文件服务、打印服务以及安全服务等。用户任务就是在计算机上用户使用的软件了,比如浏览器、QQ、微信、以及我们工程师使用的ide等都是用户任务。为了实现同时运行这么多任务,计算机科学中推出了并发的概念,它允许多个任务同时运行。多线程就是实现并发的技术之一。
二、线程类
QT中的线程类名字叫QThread。该类为QT框架提供子线程的实现,让QT编写的程序性能得到提升。开始介绍之前我先将平时可能遇到的一些线程类成员函数介绍一下:
QThread::QThread(QObject *parent = nullptr)
//构造函数,创建一个新的线程对象
void QThread::run();
//线程类中主要的函数,它本身是一个虚函数,需要使用一个派生类继承QThread,
//然后在这个派生类中重写run函数,重写的内容就是要在子线程中运行的代码。他是QT
//中创建线程的第一种方式
void QObject::moveToThread();
//该函数用于实现将一个类对象中的成员丢给一个线程进行运行。是QT中线程创建的
//第二种方式
void QThread::start();
//这个函数负责启动线程
三、QThread(创建线程的方式)
QThread为Qt框架提供的一个用于创建和管理线程的类。它封装了底层操作系统的线程相关的功能,提供了一个更高级的接口,使得在Qt应用程序中创建和管理线程更加方便和简单。
在QT中使用QThread有两种方法。一种是直接继承QThread然后重写run函数。另一种就是直接继承QObject使用QObject中的moveToThread函数,然后通过事件驱动的方式来实现多线程。
(一)方法一
方法一直接继承QThread,然后重写run函数。所以我们创建一个派生类来继承QThread,如下列代码:
class ThreadDemo : public QThread{
//成员
}
在上面的基础上在这个派生类中重写QThread中的run函数:
class ThreadDemo : public QThread{
protected:
void run(){
//需要在子线程中实现的代码
}
}
再在主线程(也就是你创建工程的时候自动生成的mainwindow)中创建子线程对象:
ThreadDemo* TD = new ThreadDemo;
之后启动这个线程就可以了,上面也介绍了,直接使用start函数就可以了:
TD -> start();
来写个简单的实例吧,这个实例就是简单的使用label控件输出循环1~999999之间的数字。但是呢!!!我如果不创建一个线程来单独处理这个任务的话,我要是在程序运行得时候产生其他的事件(用鼠标移动主界面的框)他就会停止输出,然后我停止这个新事件后,这个事件就会重新开始按顺序输出。
建立工程,然后新建一个名为“ThreadDemo”的class文件。文件内容如下:
threaddemo.h
首先第一点,这个文件中的类ThreadDemo作为派生类继承了QThread,这是很重要的。
第二点是重写QThread中的run函数,但是是在QT中写的,按照编程规范我只是先声明了一下,具体内容还没开始写。
该头文件中声明一个“data_transmission”的自定义信号,并且这个信号同时也传递一个整型的num值。

threaddemo.cpp
这里面是对头文件中run函数重写的实现。函数体为定义一个整型变量num初始化为1.
while(1)代表这是一个无限循环,这里面数字1在计算机中代表真的意思,条件为真他就会一直循环下去。
while中第一条代码为发射在头文件中声明的信号,每循环一次就发射一次并且触发一次对应的槽函数,同时向每次发射的时候向槽函数传递一个num值。
if函数是一个终止while循环的条件,当输出的值num等于99999的时候就会运行break,这个循环就会终止。
sleep(1),每循环一次此线程休眠1秒,不然主界面中要输出的数字没个几秒就刷完了。

mainwindow.cpp
首先肯定要创建自定义信号所在类的对象,就在mainwindow的构造函数中的第二段代码,new了一个。
之后就是自定义信号的连接了。
两个connect都是Lambda 槽函数。
第一个connect将继承QThread的类ThreadDemonew的一个新对象,他是信号的发送者,信号就是前面在threaddemo里面的data_transmission函数。再由mainwindow的对象自身接受(this),触发由Lambda代替的槽函数。这个Lambda函数的主体意思为一个指向主界面一个名叫label控件,而后指向一个名叫setNum的函数(这里可以理解为指向这个控件后再启动setNum函数给这个控件使用),setNum是QLabel类的一个成员函数,负责设置标签的文本内容为一个数字。并且接受一个名为num的变量,对应前面定义的num接受它的数据,数据就会通过表达式里面的式子传递给主界面中的label控件。
第二个connect发送信号的是一个按钮,发送这个按钮的clicked信号,接收对象还是mainwindow对象本身,触发Lambda表达式代表的槽函数。就是使用start函数启动这个线程。

以下是运行成功的结果:

(二)方法二
本人倾向于使用这种方式,别看方法一比较简单一些,但是方法一中多线程的使用需要将所有需要运行的代码写到run函数里面统一start。方法二唯一缺点就是麻烦了点,但是胜在更加灵活,但不是说方法一就不要用了,程序的设计就是要灵活一些。
我前面也介绍了,这继承的类为QObject,它是QT框架根类,然后直接使用moveToThread。
第一步:派生类继承QObject
class ThreadDemo02 : public QObject{
//成员
}
第二步:在这个类中创建一个公共函数。这个函数名字自己取不是格式内容。再就是函数的函数体就是需要执行的业务逻辑。
class ThreadDemo02 : public QObject{
//成员
public:
void ThreadWork();
}
第三步:在主线程中new一个QThread类的对象。记住这里
QThread* T = new QThread;
第四步:跟方法一中一样得给这个派生类new一个对象。
ThreadDemo01* TD01 = new ThreadDemo01;
第五步:上面两个对象创建成功以后就做好基本准备了,现在使用moveToThread函数将派生类对象中要运行的代码丢进新建线程类对象中运行。
TD01->moveToThread(T);
第六步:还是一样的使用start函数启动一下就行了。
T->start();
还是依旧来来个实例。虽说是实例,只不过将上面的代码改了一下,使用了方法二创建线程的格式而已。
threaddemo01.h
这里面就多添加了一个新的槽函数,需要在线程中实现的代码都在这里面。

threaddemo01.cpp
实现ThreadWork函数,函数体为方法一中的实例代码。
mainwindow.cpp
接下里上正戏了,先分别new一个QThread和ThreadDemo01的对象。
然后使用moveToThread函数将T和readDemo01丢给QThread。
之后使用start启动这个线程。
接下来是两个connect实现信号与槽。
第一个connect为了实现循环的数据传输给主界面中label控件。
第二个connect将主界面中的一个按钮控件和这个ThreadWork连接在一起,作用就是按下按钮的时候就可以执行ThreadWork了。

四、QT中线程类相关的其他函数
(一)线程优先级设置
void QThread::setPriority();
//此函数为创建的线程设置运行优先级
//操作系统中对优先级的解释是具有更
//高优先级的线程将更有可能被操作系
//统调度器选择执行,而具有较低优先
//级的线程则可能会在等待时间更长。
//该函数是有一个必须要填的参数,如下:
QThread::IdlePriority// 最低等级
QThread::LowestPriority// |
QThread::LowPriority// |
QThread::NormalPriority// |由低到高,系统对线程先运行的可能变高
QThread::HighPriority// |
QThread::HighestPriority// |
QThread::TimeCriticalPriority// v
QThread::InheritPriority// 最高等级
实例:
ThreadDemo* TD = new ThreadDemo;
TD->setPriority(QThread::LowestPriority);
(二)判断线程本身和线程中任务是否在执行
bool QThread::isFinished();
//该函数判断指定线程中的任务是否执行完成
//如果线程中任务已经完成,返回一个true,否则false
bool QThread::isRunning();
//该函数判断指定线程本身是不是在执行任务
//如果线程本身正在运行,返回一个true,否则false
(三)手动退出线程
void QThread::quit();
void QThread::exit();
//两者都是QT中用于手动退出一个线程的函数
bool QThread::wait();
//前者两个手动退出线程的函数用于请求线程退出,
//但这只是一个请求,并不代表线程立即退出。实
//际上,线程会等待当前事件循环结束后才会退出。
//因此,在某些情况下,如果你希望在线程退出后
//继续执行其他操作,就需要等待线程执行完毕。
//使用wait()可以在主线程中等待特定线程执行完毕,
//然后再继续执行其他操作。如果不调用wait(),
//主线程可能会在线程退出之前继续执行,这样可能会导致一些问题,
//比如线程未执行完毕时就进行资源释放或其他操作。
//一般都是在手动退出函数后面增加wait()。

浙公网安备 33010602011771号