Qt中多线程的使用

最近项目中提出了新的需求,需要采集和收集图片。因为是高速相机,按照帧率至少200FPS的速度计算的话,30秒时间的图片也就是6000帧,同时要保持尽量不掉帧。因此这个开发任务就有两个重点:

  • 如何能够在收集图片的时候不掉帧,把30秒钟采集到的图片尽可能的采集和收集到;
  • 在保存文件时,如何能够保证界面的及时响应,因为需要存放大量的图片,在只有一个线程工作的情况下,势必不能保持界面的良好响应;

对于第一点,个人采取的方案是只能是在采取完所有的图片之后,再将图片从内存中写入到硬盘中。这样保证了采集速率和缓速率之间的瓶颈。但是不足的是,如果按照图片的分辨率是640*480,那么6000张图片就是1.7G。所以,会占据很大的内存。同时需要考虑到合适的数据结构,对于1.7个G的数据量,如果采用数组是不合适的,因为数组是连续的分配内存空间,那么对于内存较小的内存空间,就会分配失败。那么比较好的数据结构,应该是采用链表。因此我没有采用vector的数据结构,而是采用了list的数据结构

对于第二点,当内存中已经采取到6000帧数据,从内存写入到硬盘中去的时候,会耗费比较大的时间以及CPU的运行,因此如果在单线程中,势必会影响到界面的良好响应,所以必须采用多线程来实现。目前为止,我的技能只有两个,一个是pthread线程机制,一个是不太熟悉的QThread机制,也就是Qt提供的多线程机制,因为不了解,所以我采用了pthread线程机制,但是在实现中出现了个一个重要的问题:

  • 实际上,得到需要的数据后,可以很好的保持界面的响应,同时保持“不可见的后台”写入数据的线程。这一点是实现了的。但是我还希望通过在写入的过程中,实时更新写入状态,通过进度条来显示写入进度,这样就出现了一个错误,即Qt不支持在多线程(这里的多线程指的是pthread开启的线程)之间传递信号。至于这个问题,我没有找到很好的解决方案,于是乎我希望通过一个全局变量,在两个线程之间共享全局变量的状态,这样即达到了更新写入状态的目的;
  • 对于1的解决方案,还有一个就是通过QThead线程来实现。因为QThead是继承QObject对象的,而QObject是支持Qt信号槽的接触。因此我猜想有可能会可能有用,因此去学习了如果使用Qt线程,下面介绍一下我在实际中的使用。

  • Qt线程学习

run()是线程的入口,就像main()对于应用程序的作用。QThread中对run()的默认实现调用了exec(),从而创建一个QEventLoop对象,由其处理该线程事件队列(每一个线程都有一个属于自己的事件队列)中的事件,用代码简单描述过程如下

由此可见,exec()在其内部不断做着循环遍历事件队列的工作,调用QThread的quit()或exit()方法使停止工作,尽量不要使用terminate(),该方法过于粗暴,造成资源不能释放,甚至互斥锁还处于加锁状态。

  • 关于QThread使用的简单讨论

一般而言,我们会去继承QThread,然后将线程运行需要的参数传递进去,然后调用run方法,内部调用成员变量运行。比较常见的案例如下:

那么这个简单的案例最大的难点以及疑点就是moveThread的使用。为何需要这么做呢?以下内容摘自网页http://blog.jobbole.com/84149/,我任务讲得蛮好的,因此分享给大家:

moveToThread()函数通知Qt准备好事件处理程序,让扩展的信号(signal)和槽(slot)在指定线程的作用域中调用。QThread是线程的接口,所以我们是在告诉这个线程在“它内部”执行代码。我们也应该在线程运行之前做这些事。即使这份代码看起来可以运行,但它很混乱,并不是QThread设计中的用法(QThread中写的所有函数都应该在创建它的线程中调用,而不是QThread开启的线程)。

QThread是被设计来作为一个操作系统线程的接口和控制点而不是用来写入你想在线程里执行的代码的地方。我们(面向对象程序员)编写子类,是因为我们想扩充或者特化基类中的功能。我唯一想到的继承QThread类的合理原因,是添加QThread中不包含的功能,比如,也许可以提供一个内存指针来作为线程的堆栈,或者可以添加实时的接口和支持。用于下载文件、查询数据库,或者做任何其他操作的代码都不应该被加入到QThread的子类中;它应该被封装在它自己的对象中。
通常,你可以简单地把类从继承QThread改为继承QObject,并且,也许得修改下类名。QThread类提供了start()信号,你可以将它连接到你需要的地方来进行初始化操作。为了让你的代码实际运行在新线程的作用域中,你需要实例化一个QThread对象,并且使用moveToThread()函数将你的对象分配给它。你同过moveToThread()来告诉Qt将你的代码运行在特定线程的作用域中,让线程接口和代码对象分离。如果需要的话,现在你可以将一个类的多个对象分配到一个线程中,或者将多个类的多个对象分配到一个线程。换句话说,将一个实例与一个线程绑定并不是必须的

因此合理使用moveThread是QThread需要注意的地方。

下面贴出我自己在使用QThread的案例,不知大家还记得我最开始提出我在使用多线程的难点,即共享线程间的状态,即线程间发送信号。因此在这里,我应该是希望我创建的线程依赖于我创建它的线程,这样我创建线程发送的信号就可以被我的主线程中捕捉使用。因此moveThread是不必须的。

具体实现:

#include "savethread.h"

SaveThread::SaveThread(QObject *parent)
	: QThread(parent)
{
	IsSaving=false;
}

SaveThread::~SaveThread()
{

}

void SaveThread::SetParameter(std::list<cv::Mat>buffer,QString format)
{
	buffer_temp=buffer;
	this->format=format;
}

bool SaveThread::GetStatus()
{
	return IsSaving;
}

void SaveThread::run()
{
	int i=1;
	IsSaving=true;
	std::string picName;
	while(!buffer_temp.empty())
	{
		cv::Mat temp=buffer_temp.back();
		Mat finalMat=seprateImage(temp);
		picName="./图像采集/";
		picName+=QString::number(i).toStdString();
		picName+=format.toStdString();
		cv::imwrite(picName,finalMat);
		buffer_temp.pop_back();
		emit sig_progress_value(i);
		i=i+1;
		
	}
	IsSaving=false;
}


cv::Mat SaveThread::seprateImage(cv::Mat srcImage)
{
	int height=480;
	int width=648;
	Mat temp_right=cv::Mat::zeros(height,width/2,CV_8UC1);
	Mat finalMat=Mat::zeros(height,width,CV_8UC1);
	if(width%2==0)
	{
		int j=0;
		int k=width/2-1;
		for(int i=0;i<srcImage.cols;i++)
		{
			if(i%2==0)
			{
				srcImage.col(i).copyTo(finalMat.col(j));j++;
			}

			else
			{
				srcImage.col(i).copyTo(temp_right.col(k));k--;
			}
		}

		for(int i=width/2;i<width;i++)
		{
			temp_right.col(i-width/2).copyTo(finalMat.col(i));
		}
	}
	return finalMat;
}
在主线程中调用:

saveThread=new SaveThread(this);
saveThread->SetParameter(buffer,format);
connect(saveThread,SIGNAL(sig_progress_value(int)),this,SLOT(onUpdateProgress(int)));
saveThread->start();

以上是大致的实现逻辑,具体实现的细节有点多,篇幅有限,可以给大家看看最后的效果:因为传递图片大小的限定,不能给出较长的时间长度的显示,所以给了大致的显示:




  • 22
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值