在运行Qt程序时遇到一个问题:在没有鼠标或触摸屏操作的时候,Qt界面显示USB摄像头的视频很流畅。但是一旦有鼠标操作或者触摸屏操作的时候,就会出现Qt视频流停止运行的现象;而且只有鼠标或者触摸屏有事件发生的时候,Qt界面视频流才会一帧一帧的运行。感觉很奇怪!先试试多线程看看吧!
本文参考Qt使用多线程的一些心得——2.继承QObject的多线程使用方法
2018.1.26补充:在学习了继承使用QObject来实现多线程之后,一直频繁的使用,在这过程中,经常遇到这个问题:在主线程中创建多线程,然后主线程假死。
原因很简单:在主线程中创建了线程对象,在线程对象的构造函数中调用了线程中消耗CPU时间的函数,而构造函数是在主线程中运行的,所以调用的函数其实还是在主线程中运行,结果计算量过大的函数就导致主线程的事件循环进入假死状态。
解决方法就是:在主线程中创建线程对象,但是不要在构造函数中调用耗时函数,如果调用,其实还是在主线程的事件循环当中。我们应该在主线程中使用一个信号,来触发子线程中的耗时函数。
QObject
QObject
是Qt
框架的基本类,但凡涉及到信号与槽有关的类都是继承于QObject
。QObject
提供了Qt
关键技术信号与槽的支持以及事件系统的支持,同时它提供了线程操作的接口,也就是QObject
可以选择在不同的线程中执行。
QObject
的线程转移函数是:void moveToThread(QThread* targetThread)
,通过此函数可以把一个顶层Object
(就是没有父级)转移到一个新的线程中。
QObject
的多线程实现用QObject
来实现多线程默认支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer
、QTcpSocket
),QThread
要支持事件循环需要在QThread::run()
中调用QThread::exec()
,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号与槽,直接使用QObject
来实现多线程。
QObject
的线程转移函数:void moveToThread(QThread* targetThread)
,通过此函数可以把一个顶层QObject转移到一个新线程里。
QThread
自身并不生存在它run
函数所在的线程,而是生存在旧的线程中。
QObject
多线程的方法创建线程要让QThread
的start
函数运行起来,但是需要注意销毁线程的方法。在线程创建之后,QObject
的销毁不应该在主线程中进行,而是通过deleteLater
槽进行安全的销毁。因为,继承QObject
多线程的方法在创建时有几个槽函数需要特别注意: QThread
的finished
信号对接QObject
的deleteLater
使得线程结束后,继承QObject
的那个多线程类会自己销毁。 QThread
的finished
信号对接QThread
自己的deleteLater
。使用QObject
创建多线程的方法如下:
QObject
的类,对需要进行复杂耗时逻辑的入口函数声明为槽函数。new
出来,不能给它设置任何父对象QThread
,如果不是new
,在析构时需要调用QThread::wait()
,如果是堆分配的话,可以通过deleteLater
让线程自动销毁。obj
通过moveToThread
方法转移到新线程中,此时object
已经在线程中。finished
信号和object
的deleteLater
槽连接,这个信号槽必须连接,否则会内存泄漏。QThread::start()
来启动线程QThread::quit
退出线程的事件循环 QObject
来实现多线程比用继承QThread
的方法更加灵活,整个类都是在新的线程中,通过信号槽和主线程传递数据。#ifndef THREADOBJECT_H
#define THREADOBJECT_H
#include
#include
class ThreadObject : public QObject
{
Q_OBJECT
public:
ThreadObject(QObject* parent = NULL);
~ThreadObject();
void setRunCount(int count);
void stop();
signals:
void message(const QString& info);
void progress(int present);
public slots:
void runSomeBigWork1();
void runSomeBigWork2();
private:
int m_runCount;
int m_runCount2;
bool m_isStop;
QMutex m_stopMutex;
};
#endif // THREADOBJECT_H
#include "ThreadObject.h"
#include
#include
#include
#include
#include
#include
ThreadObject::ThreadObject(QObject *parent):QObject(parent)
,m_runCount(10)
,m_runCount2(std::numeric_limits<int>::max())
,m_isStop(true)
{
}
ThreadObject::~ThreadObject()
{
qDebug() << "ThreadObject destroy";
emit message(QString("Destroy %1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg(*(int*)QThread::currentThreadId()));
}
void ThreadObject::setRunCount(int count)
{
m_runCount = count;
emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg(*(int*)QThread::currentThreadId()));
}
void ThreadObject::runSomeBigWork1()
{
{
QMutexLocker locker(&m_stopMutex);
m_isStop = false;
}
int count = 0;
QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg(*(int*)QThread::currentThreadId());
emit message(str);
int process = 0;
while(1)
{
{
QMutexLocker locker(&m_stopMutex);
if(m_isStop)
return;
}
if(m_runCount == count)
{
break;
}
sleep(1);
int pro = ((float)count / m_runCount) * 100;
if(pro != process)
{
process = pro;
emit progress(((float)count / m_runCount) * 100);
emit message(QString("Object::run times:%1,m_runCount:%2").arg(count).arg(m_runCount2));
}
++count;
}
}
void ThreadObject::runSomeBigWork2()
{
{
QMutexLocker locker(&m_stopMutex);
m_isStop = false;
}
int count = 0;
QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg(*(int*)QThread::currentThreadId());
emit message(str);
int process = 0;
QElapsedTimer timer;
timer.start();
while(1)
{
{
QMutexLocker locker(&m_stopMutex);
if(m_isStop)
return;
}
if(m_runCount2 == count)
{
break;
}
int pro = ((float)count / m_runCount2) * 100;
if(pro != process)
{
process = pro;
emit progress(pro);
emit message(QString("%1,%2,%3,%4")
.arg(count)
.arg(m_runCount2)
.arg(pro)
.arg(timer.elapsed()));
timer.restart();
}
++count;
}
}
void ThreadObject::stop()
{
QMutexLocker locker(&m_stopMutex);
emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg(*(int*)QThread::currentThreadId()));
m_isStop = true;
}
可以知道,在这个继承OObject的类中,并没有继承和线程相关的东西,只是一个普通的类而已。但是通过moveToThread
,这个类的功能就在一个线程中运行了。
void Widget::startObjThread()
{
if(m_objThread)
{
return;
}
m_objThread = new QThread(); // deleteLater make thread kill itself
m_obj = new ThreadObject(); // new thread inherit from QObject, without parent object
m_obj->moveToThread(m_objThread);
connect(m_objThread, &QThread::finished, m_objThread, &QObject::deleteLater);
connect(m_objThread, &QThread::finished, m_obj, &QObject::deleteLater);
connect(this, &Widget::startObjThreadWork1, m_obj, &ThreadObject::runSomeBigWork1);
connect(this, &Widget::startObjThreadWork2, m_obj, &ThreadObject::runSomeBigWork2);
connect(m_obj, &ThreadObject::progress, this, &Widget::progress);
connect(m_obj, &ThreadObject::message, this, &Widget::receiveMessage);
m_objThread->start();
}
这个函数在某个槽中被调用,然后按照这个流程即可建立一个继承QObject
的线程。主要就是
m_objThread = new QThread(); // deleteLater make thread kill itself
m_obj = new ThreadObject(); // new thread inherit from QObject, without parent object
m_obj->moveToThread(m_objThread);
connect(m_objThread, &QThread::finished, m_objThread, &QObject::deleteLater);
connect(m_objThread, &QThread::finished, m_obj, &QObject::deleteLater);
使用QThread
需要调用QThread::exec()
,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号与槽,直接使用QObject
来实现多线程。
Qt使用多线程的一些心得——1.继承QThread的多线程使用方法
QThread详解