QT5多线程学习(多文件同步处理)

在QT开发过程中遇到耗时的操作一定要使用“多线程技术”,可以避免GUI无响应和卡死的问题。

下面使用一个小Demo来模拟多线程下多文件的处理过程

一、 设计思路

在主窗体上利用QListWidget控件获取文件列表,将m个文件分割成若干份,平均分配到不同的子线程中去并行处理。主界面上支持人工设置开启的线程数量。

在这里插入图片描述

程序界面如下图所示:
在这里插入图片描述
二、代码实现

不多BB了,直接挒上代码!!!

新建一个子线程类(class MyThread)继承于QObject

MyThread.h

#include <QObject>
#include <qthread.h>

class MyThread : public QObject
{
	Q_OBJECT
public:

	explicit MyThread(QObject *parent = nullptr);
	~MyThread();

	void SetFlag(bool flag)
	{
		this->mIsStop = flag;
	}

signals:
	void mySignal(int index);
	void fileNameSignal(QString name);

public slots:

	//线程处理函数
	void doProcess(QStringList fileList, int index);

private:
    // 标志位
	bool mIsStop = false;
};

MyThread.cpp

#include "MyThread.h"
#include "qdebug.h"

MyThread::MyThread(QObject *parent)
	: QObject(parent)
{
}

MyThread::~MyThread()
{
}


void MyThread::doProcess(QStringList fileList, int index)
{
	// 利用while循环和标志位mIsStop来结束子线程的循环
	while (!this->mIsStop)
	{
		for (int i = 0; i < fileList.size(); ++i)
		{
			QThread::sleep(3);   // 耗时的操作,睡眠3s(在此处可以封装一个数据处理算法类)
			qDebug() << fileList[i] << QThread::currentThread(); // 打印子线程地址
			emit fileNameSignal(fileList[i]);
		}
		this->mIsStop = true;
		
	}
	
	// 发送信号给主线程
	emit mySignal(index);
}

主窗体类 class ThreadWidget

ThreadWidget.h

#include <QtWidgets/QWidget>
#include "ui_ThreadWidget.h"
#include "qstringlist.h"
#include "qlist.h"
#include <time.h>

class MyThread;
class QThread;

class ThreadWidget : public QWidget
{
    Q_OBJECT

public:
    ThreadWidget(QWidget *parent = Q_NULLPTR);
	~ThreadWidget();

signals:
	void startSianal(const QStringList fileList, int index);

public slots:
	void onThreadBtn();
	void dealSignal(int index);
	void onProgress(QString fileName);

private:
	Ui::ThreadWidgetClass mUi;
	Params mParams;
	clock_t mStartTime;
	clock_t mEndTime;

	// QList存放线程对象,用于同时开启多个
	QList<MyThread*> mMyThreadList;
	QList<QThread*> mQThreadList;
	

	void InitThread();  // 线程初始化
	void SplitFileList(const QStringList& fileList, QList<QStringList>& allFileList);
};

ThreadWidget.cpp

#include "ThreadWidget.h"
#include "qfiledialog.h"
#include "MyThread.h"
#include "qmessagebox.h"

ThreadWidget::ThreadWidget(QWidget *parent)
    : QWidget(parent)
{
    mUi.setupUi(this);
	
	connect(mUi.threadBtn, &QAbstractButton::clicked, this, &ThreadWidget::onThreadBtn);

	// 文件对话框
	connect(mUi.openBtn, &QPushButton::clicked, [=]() {
		QStringList s = QFileDialog::getOpenFileNames(this, QStringLiteral("文件打开"), "C:\\Users\\xjm\\Desktop", "*.las;;*.txt;;*.docx");
		mUi.listWidget->addItems(s);

		mParams.setFileList(s);
	});

	// 清空文件列表
	connect(mUi.clearBtn, &QPushButton::clicked, [=]() {
		mUi.listWidget->clear();
		mParams.Clear();
	});

	// 删除选中的文件
	connect(mUi.removeBtn, &QPushButton::clicked, [=]() {
		
		int row = mUi.listWidget->currentRow();
		mUi.listWidget->takeItem(row);
		mParams.Remove(row);
	});


	// QSpinBox的信号valueChanged发生了重载,重载形式1:valueChanged(const QString&)   重载形式2:valueChanged(const int)
	// 所以此处需要利用函数指针来区分到底使用哪个重载版本!!!
	void(QSpinBox:: *spinBoxSignal)(int) = &QSpinBox::valueChanged;
	connect(mUi.spinBox, spinBoxSignal, this, [=](int k) {
		mParams.setThreadNums(k);
	});

	mParams.setThreadNums(mUi.spinBox->value());
}


ThreadWidget::~ThreadWidget()
{
	for (int i = 0; i < mMyThreadList.size(); ++i)
	{
		if (mMyThreadList[i])
		{
			delete mMyThreadList[i];
			mMyThreadList[i] = nullptr;
		}
	}
	
}


void ThreadWidget::onThreadBtn()
{
	// 计时开始
	mStartTime = clock();

	// 多线程初始化
	InitThread();

	mUi.timeLabel->setText(QString::number(0) + QStringLiteral("秒"));
	mUi.progressBar->setValue(0);

	// 若有线程正在运行,则返回
	for (int i = 0; i < mParams.getThreadNums(); ++i)
	{
		if (mQThreadList[i]->isRunning() == true)
		{
			return;
		}
	}
	

	// 文件列表分割
	QList<QStringList> allFileList;    // allFileList[i]表示第i+1个线程中需要处理的文件
	SplitFileList(mParams.GetFileList(), allFileList);


	// 循环启动所有线程(注:当子线程在运行时,主线程内的这个for循环也会同步运行,达到多个线程并行的效果)
	for (int i = 0; i < mParams.getThreadNums(); ++i)
	{
		// 启动
		mQThreadList[i]->start();  

		//注意connect函数的第三个参数是不同的自定义线程对象mMyThreadList[i]
		connect(this, &ThreadWidget::startSianal, mMyThreadList[i], &MyThread::doProcess); 
		
		// 发送线程启动信号startSianal给线程处理函数doProcess
		emit startSianal(allFileList[i], i);
	}
}


void ThreadWidget::InitThread()
{
	// 清空上一次的线程对象
	mMyThreadList.clear();
	mQThreadList.clear();

	// 预分配内存
	mMyThreadList.reserve(mParams.getThreadNums());
	mQThreadList.reserve(mParams.getThreadNums());


	for (int i = 0; i < mParams.getThreadNums(); ++i)
	{
		// 自定义线程对象分配内存(不指定父对象,析构函数内自己释放)
		MyThread* myT = new MyThread;
		
		// QThread对象分配内存(指定父对象)
		QThread* qT = new QThread(this);
		mMyThreadList << myT;
		mQThreadList << qT;

		// 将自定义线程对象myT与QThread对象qT关联起来!
		mMyThreadList[i]->moveToThread(mQThreadList[i]);


		// 子线程与主线程通信
		connect(mMyThreadList[i], &MyThread::mySignal, this, &ThreadWidget::dealSignal);
		connect(mMyThreadList[i], &MyThread::fileNameSignal, this, &ThreadWidget::onProgress);
	}
}


void ThreadWidget::onProgress(QString fileName)
{
	// 利用已处理完毕的文件名称来控制进度条

	static QStringList xjm;
	if (!xjm.contains(fileName))
	{
		xjm << fileName;
	}

	mUi.progressBar->setValue((100 / mParams.GetFileList().size())*xjm.size());

	if (xjm.size() == mParams.GetFileList().size())
	{
		xjm.clear();
		mUi.progressBar->setValue(100);
		mEndTime = clock();
		int t = static_cast<int>(((double)(mEndTime - mStartTime)) / CLOCKS_PER_SEC);
		mUi.timeLabel->setText(QString::number(t) + QStringLiteral("秒"));
		QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("完成!"));
	}
}



void ThreadWidget::dealSignal(int index)
{
	// index是线程序号,利用index确定需要关闭的线程。
	mQThreadList[index]->quit();
	mQThreadList[index]->wait();
}


void ThreadWidget::SplitFileList(const QStringList& fileList, QList<QStringList>& allFileList)
{
	// 单个线程里面处理a个文件
	int a = static_cast<int>(mParams.GetFileList().size() / mParams.getThreadNums()); 
	
	// 最后一个线程里面处理a+b个文件
	int b = mParams.GetFileList().size() % mParams.getThreadNums(); 

	for (int i = 0; i < mParams.getThreadNums(); ++i)
	{
		QStringList temp;
		for (int j = 0; j < a; ++j)
		{
			temp << fileList[i*a + j];
		}

		if (i == mParams.getThreadNums() - 1)
		{
			for (int k = 1; k <= b; ++k)
			{
				temp << fileList[fileList.size() - k];
			}
		}
		allFileList << temp;
	}
}

参数类 class Params

class Params
{
public:
	Params() {}
	~Params(){}

	void setFileList(const QStringList& fileList)
	{
		for (int i = 0; i < fileList.size(); ++i)
		{
			mFileList << fileList[i];
		}
	}
	
	const QStringList GetFileList()
	{
		return mFileList;
	}

	void Clear()
	{
		mFileList.clear();
	}

	void Remove(int k)
	{
		mFileList.removeAt(k);
	}


	void setThreadNums(const int& num)
	{
		mThreadNums = num;
	}

	const int getThreadNums()
	{
		return mThreadNums;
	}


private:
	QStringList mFileList;
	int mThreadNums;
};

三、测试

已知子线程中每块数据处理需要3秒,当文件列表中有10块数据时,开启5个线程同步处理,每个线程中分配2块数据,则理论上一共耗时2*3=6秒.

在这里插入图片描述

控制台下打印5个子线程的地址,不难发现10块数据的处理顺序不是按照主界面上文件排列顺序来的,所以多线程下的处理是没有顺序可言的。
文件1、文件2 ——> 子线程 0x158b2a968a0
文件3、文件4 ——> 子线程 0x158b2a96aa0
文件5、文件6 ——> 子线程 0x158b2a96b40
文件7、文件8 ——> 子线程 0x158b2a96bc0
文件9、文件10 ——> 子线程 0x158b2a96da0
在这里插入图片描述

再次验证:

开启3个线程同时处理10块数据,则线程1和线程2中分配到3块数据,线程3中分配到4块数据。所以理论上处理时间为4*3=12秒。

在这里插入图片描述

文件1、文件2、文件3 ——> 子线程 0x245f9c37510
文件4、文件5、文件6 ——> 子线程 0x245f9c37630
文件7、文件8、文件9、文件10 ——> 子线程 0x245f9ca70
在这里插入图片描述

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值