一 前言
本篇文章部分内容参考了该博文:传送门。
Qt中有两种多线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。
Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。两种方法的区别不大,用起来都比较方便,但继承QObject的方法更加灵活。这里要记录的是如何正确地创建一个线程,以及如何正确地退出一个线程。
本文将以一个实际的工程项目为例,重点介绍QThread的普通用法,也是属于多线程最基本也最简单的一种用法,但是具有代表性和典型性。
二Qt多线程方法一 继承QThread
在使用继承QThread的run方法之前,我们需要了解一条规则:
QThread只有run函数是在新的线程里,其他所有函数都在QThread生成的线程里。
如果QThread是在ui所在的线程里生成,那么QThread的其他非run函数都是和ui线程一样的,所以,QThread的继承类的其他函数尽量别要有太耗时的操作,要确保所有耗时的操作都在run线程里。(因而,大家应该能明白:线程是专门用来处理耗时过长,防止界面卡死的)
在UI线程下调用QThread的非run函数(其实也不应该直接调用run函数,而应该使用start函数——run是由start触发运行的),和执行普通函数无区别,这时,如果这个函数要对QThread的某个变量进行变更,而这个变量在run函数里也会被用到,这时就需要注意加锁的问题,因为可能这个变量前几毫秒刚刚在run中调用,再调用时已经被另外的线程修改了。关于加锁(QMutex)的使用,接下来的例子中不涉及,如果感兴趣,可以自行研究。
2.1使用多线程的原因分析
接下来的示例来自于一个工程实践:
在QT工程中,编写一个界面,来通过外触发让两个PointGrey相机拍摄两千张图片,并保存到本地。(外触发拍摄的帧率为固定帧率:30帧/s)。界面如下:
根据帧率与拍摄总张数可知,如果不用多线程,当我点击外触发拍摄按钮,那么程序将要等待时长为:2000/30≈66.67s,界面注定要卡死。
2.2 写一个继承于QThread的线程
步骤归纳总结如下:
1)在工程中添加一个类CamThread(Qt5Class):
此处我将自己添加的类(.h和.cpp文件呈现如下)
CamThread.h文件如下:
#ifndef CAMTHREAD_H
#define CAMTHREAD_H
#include<opencv2/opencv.hpp>
#include <QThread>
//工程内头文件
#include"pcamera.h"
//定义枚举变量 以表示左右相机
enum LRThread
{
L, R
};
class CamThread : public QThread
{
Q_OBJECT
public:
CamThread(QObject *parent); //默认构造函数
CamThread(LRThread flagL,LRThread flagR); //构造函数的重载
~CamThread();
public:
PCamera cameraL;
PCamera cameraR;
string nameTemL;
string nameTemR;
//外触发拍摄张数
int trigerNum;
//定义一个bool变量runFlag来控制线程的通断
bool runFlag;
public:
//通过改变bool变量,来控制线程的通断
void stopImmediately();//---------自己定义的函数,控制通断
signals:
//定义了一个信号,在线程结束时,给ui界面发送信号,
//ui接收信号后,便可以显示消息对话框
void threadFinished();//-------定义了一个信号
public:
//两个相机同时外触发拍摄
void run();//----------------主要函数
private:
};
#endif // CAMTHREAD_H
CamThread.cpp文件:
#include "CamThread.h"
#include"dynamictrackdlg.h"
//#define EXTERNAL
#define SORFWARE
using namespace cv;
CamThread::CamThread(QObject *parent)
: QThread(parent)
{
}
CamThread::CamThread(LRThread flagL, LRThread flagR)
{
runFlag = true;
//连接两个相机
if (flagL==LRThread::L)
{
nameTemL = "D:/DataFiles/triger/L/image";
cameraL.ConnectToCamera(0);
}
if (flagR==LRThread::R)
{
nameTemR = "D:/DataFiles/triger/R/image";
cameraR.ConnectToCamera(1);
}
}
CamThread::~CamThread()
{
}
void CamThread::stopImmediately()
{
runFlag = false;
}
void CamThread::run()
{
char imgNameL[50], imgNameR[50];
Image imgL, imgR;
while (runFlag)
{
for (size_t i = 1; i < trigerNum; i++)
{
//使用软触发
#ifdef EXTERNAL
cameraL.m_Cam.GrabAPicture(imgL);
cameraR.m_Cam.GrabAPicture(imgR);
#endif //软触发
#ifdef SORFWARE
cameraL.m_Cam.RetrieveBuffer(&imgL);
cameraR.m_Cam.RetrieveBuffer(&imgR);
#endif //外触发
sprintf(imgNameL, "%s%d%s", nameTemL.data(), i, ".bmp");
sprintf(imgNameR, "%s%d%s", nameTemR.data(), i, ".bmp");
imgL.Save(imgNameL);
imgR.Save(imgNameR);
imgL.ReleaseBuffer();
imgR.ReleaseBuffer();
if (runFlag==false)
{
return;
}
}
emit threadFinished();
stopImmediately();
}
}
相信大家看到上面的.h和.cpp文件,可能对如何开启线程和如何退出线程还是一筹莫展的,别急,下面给您梳理一下。
外触发控件对应的响应函数为:(包含了多线程的开启与结束时的消息响应)
//外触发
void Dynamic3DTracking::on_externalTrigButton_clicked()
{
……
//此处将thread在 Dynamic3DTracking.h中定义,
//作为全局变量,方便后续的线程关闭
thread =new CamThread(LRThread::L, LRThread::R);
//connect将线程结束发射的threadFinished()与在
// Dynamic3DTracking.h中定义的槽函数关联,以便通知ui显示线程结束标识。
connect(thread, SIGNAL(threadFinished()), this, SLOT(receivedSlot()));
//线程的开始 ,start()会自动触发run()函数的运行
thread->start();
}
界面的主界面名为:dynamic3dtracking
此处需要在dynamic3dtracking.h中先定义一个多线程变量:
public:
CamThread *thread;
同时也需要在dynamic3dtracking.h中定义一个槽函数,以用来响应线程结束之后发出的信号,做出显示“拍照结束”的消息窗口
private slots:
void receivedSlot();
dynamic3dtracking.cpp中receivedSlot()对应的函数体为:
void Dynamic3DTracking::receivedSlot()
{
delete thread; //此处是为了将开辟的线程清理掉,不然会消耗内存。
QMessageBox::information(NULL, GBK::ToUnicode("友情提示"), GBK::ToUnicode("图片已经拍摄完成"));
}
三 总结
根据上述多线程的创建与销毁代码,可以得出QT中继承QThread实施多线程步骤如下:
1)创建一个类,继承于QThread;
2)在ui中定义与开启;
3)线程结束后,通过发送信号,来提醒ui线程的结束,并及时清理。