Qt中使用多线程的一些心得(一)——继承QThread的多线程使用方法

一 前言

  本篇文章部分内容参考了该博文:传送门
  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线程的结束,并及时清理。

  个人公众号:

这里写图片描述

猜你喜欢

转载自blog.csdn.net/yong_qi2015/article/details/79271242