为什么要做多线程,说个最简单的道理就是我们不希望在软件处理数据的时候界面处于无法响应的假死状态。有些处理是灰常花时间的,如果把这样的处理放到主线程中执行,就会导致软件一条路走到底,要等到处理完才能接收响应其他操作,这时候就会导致界面假死。
Qt提供了一个方法用于在提醒软件先处理下未处理的事情,QApplication::processEvents(),所以,如果在主线程中处理一些耗时操作,可以试下频繁的调用QApplication::processEvents()来提醒系统去处理那些未处理的事。但是这个方法毕竟事不靠谱的,某些情况还勉强可以处理,如果耗时操作封装在一个函数接口里,而我们只能去调用它的时候,这个方法就灰常无效,最终还是得用上多线程的方法。
1、QThread
这种方法跟C++的Thread操作时类似的,定义一个类继承QThread,然后重写run()方法,操作几乎是跟C++的Thread一样的。不过这种方法我现在用的很少,几乎就没怎么使用了。
所以重点还是说下第二种方法。
2、moveToThread
这种方法真的是很方便的了。操作就是先定义一个继承QObject的类,然后通过QObject的moveToThread()函数将对象移到一个子线程中就可以实现多线程了。这么简单的方法当然是要跟Qt的信号与槽来配合使用啦。
首先,我们定义一个处理子类继承QObject,然后将我们要放到子线程中执行的操作写到一个槽函数中,然后在主线程中需要处理数据的时候就先将数据传入后通过发射信号令其执行,然后子线程执行完之后发送一个信号通知主线程做出响应即可。
class SeetaFaceThread : public QObject
{
Q_OBJECT
public:
SeetaFaceThread(QObject *parent);
~SeetaFaceThread();
public slots:
void detectFaceSlot(void);
}
这样定义好一个线程类即可了,然后使用的时候,就实例化一个SeetaFaceThread对象和一个QThread对象,然后调用moveToThread()函数将其移到该子线程中,然后通过信号槽函数启动线程,这个做法很多教程都说的很仔细了。然后我想说的是另一种做法,把QThread对象在QObject的子类中实现,这样,在实现QObject的子类的时候就不再需要再去定义一个QThread,使得QObject的子类实现起来更加简单,而不需要考虑线程的结束问题。这个时候需要在QObject的子类中实例化一个QThread的对象,并将QObject的子类moveToThread(),然后需要析构函数里将线程释放。具体的实现如下,在头文件里声明一个QThread类:
QThread *m_pThread;
然后在源文件的构造函数里实例化对象:
m_pThread = new QThread(this);
m_pThread->start();
this->moveToThread(m_pThread);
在析构函数里释放线程:
m_pThread->quit();
m_pThread->wait();
delete m_pThread;
m_pThread = NULL;
这样在构造QObject的子类的时候其实就已经将子类移到子线程中了,而且,在释放内存的时候也会停止子线程并回收资源。然后在使用的时候,直接对QObject的子类进行实例化,然后记得要构造一些信号,传递会主线程,好让主线程知道处理的进度:
m_pSeetaFaceThread = new SeetaFaceThread(this);
connect(m_pSeetaFaceThread,
SIGNAL(faceDetectedFinishedSig(bool)),
this,
SLOT(faceDetectedFinishedSlot(bool)));
connect(m_pSeetaFaceThread,
SIGNAL(faceRecognizerFinishedSig(QStringList)),
this,
SLOT(faceRecognizerFinishedSlot(QStringList)));
connect(m_pSeetaFaceThread,
SIGNAL(extractingInforSig(QString)),
this,
SLOT(extractingInforSlot(QString)));
上面我在子类里定义了三个信号,主要是要通知主线程处理的结果或者一些处理信息。
然后什么时候或者怎么启动线程呢。一般设定一个信号连接要进行处理的槽函数,同时,要进行处理,应该要让子类得到处理的数据吧,一般时可以用信号槽来传递一些变量的,但是这里不太推荐,因为有些数据其实时复杂变量,信号槽有时候时不起作用的,所以,我一般是在子类里添加一些共有成员方法用来传递数据的,传递数据之后使用QTimer::singleShot()函数来作为信号源,并传递给对应槽函数。QTimer::singleShot()函数的原型有几个,主要用的是下面这个:
static void singleShot(int msec, const QObject *receiver, const char *member);
第一个变量是毫秒时间,一般设置为0就是马上传递信号,后面两位是接收器和槽函数,也就是QObject的子类和对应的槽函数。用法类似这样:
m_pSeetaFaceThread->setFileInfoList(filelist);
QTimer::singleShot(0, m_pSeetaFaceThread, SLOT(extractFeaturesSlot(void)));
这里掐掉一些东西后,固定的套路使用如下:
seetafacethread.h:
#ifndef SEETAFACETHREAD_H
#define SEETAFACETHREAD_H
#include <QObject>
class SeetaFaceThread : public QObject
{
Q_OBJECT
public:
SeetaFaceThread(QObject *parent);
~SeetaFaceThread();
signals:
void faceDetectedFinishedSig(bool);
void faceRecognizerFinishedSig(QStringList);
void extractingInforSig(QString);
public slots:
void detectFaceSlot(void);
void alignmentFaceSlot(void);
void extractFeaturesSlot(void);
void recognizerFaceSlot(void);
private:
QThread *m_pThread;
};
#endif // SEETAFACETHREAD_H
seetafacethread.cpp:
#include "seetafacethread.h"
#include <QThread>
SeetaFaceThread::SeetaFaceThread(QObject *parent)
: QObject(parent)
{
m_pThread = new QThread(this);
m_pThread->start();
this->moveToThread(m_pThread);
}
SeetaFaceThread::~SeetaFaceThread()
{
m_pThread->quit();
m_pThread->wait();
delete m_pThread;
m_pThread = NULL;
}
void SeetaFaceThread::detectFaceSlot(void)
{
//一些处理
//处理完成
emit faceDetectedFinishedSig(true);
}
void SeetaFaceThread::alignmentFaceSlot(void)
{
//一些处理
//处理完成
emit faceDetectedFinishedSig(true);
}
void SeetaFaceThread::extractFeaturesSlot(void)
{
//一些处理
//处理完成
emit extractingInforSig(QString::fromLocal8Bit("完成全部特征提取"));
emit extractingInforSig(QString::fromLocal8Bit(""));
}
void SeetaFaceThread::recognizerFaceSlot(void)
{
//一些处理
//处理完成
emit faceRecognizerFinishedSig(strList);
}
然后在需要处理数据的地方调用QTimer::singleShot()函数来执行即可。
Qt多线程还有另外两种做法:继承QRunnable和使用QtConcurrent::run,这个还是可以参考这篇博客的,但是,这两种方法,应该说QtConcurrent::run会有吸引力一些,查了一下,Qt助手的解释是:“The Qt Concurrent module extends the basic threading support found in Qt Core module and simplifies the development of code that can be executed in parallel on all available CPU cores.”也就是说,QtConcurrent扩展了Qt Core中的线程操作,简化了可在所有可用CPU内核上并行执行的代码开发。后面在对它进行深入研究吧。
你站在桥上看风景,
看风景的人在楼上看你。
明月装饰了你的窗子,
你装饰了别人的梦。
--卞之琳 《断章》