在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