本文的示例项目工程在文章最后有分享链接,可以下载运行试试。下载后替换成自己的Qt版本即可(项目 -> 属性 -> 配置属性 -> Qt Project Settings -> Qt Installation)
应用场景介绍
现有一个应用场景,需要进行上千个循环里面,调用相同的功能类去计算,只使用一个线程去计算,效率太低,结合网络上的帖子和自身实践。我的实现场景如下:
- 界面类Dialog。给用户提供交互操作
- 计算类Logic。执行用户点击后的计算逻辑
- 任务类QRunnable。在若干循环里面用于完成指定任务
运行过程
在Qt界面中,用户点击某个控件后,创建一个线程去执行计算操作,避免界面阻塞,陷入卡死。
(这里有个不继承 qthread 使用多线程的方法,提供另一种实现方法思路)
QThread* mThread; //!< 计算类执行的线程
Calculate* mCalculate; //!< 计算类,放在mThread线程里面执行,避免界面卡死
/************************************************************************/
/* 上面为成员变量 */
/************************************************************************/
mThread = new QThread();
mCalculate = new Calculate();
mCalculate->moveToThread(mThread); // 改变mCalculate的线程依附关系,将计算类放在线程中执行
//! 释放堆空间资源,避免内存泄露
connect(mThread, &QThread::finished, mThread, &QObject::deleteLater);
connect(mThread, &QThread::finished, mCalculate, &QObject::deleteLater);
//! 注意:在使用跨线程通信时,参数需要为元数据类型(具体我也解释不清楚,理解是有关 meta 什么的)
qRegisterMetaType<CalculateInputStruct>("CalculateInputStruct"); // 将结构体 CalculateInputStruct 注册为元数据类型
//! 连接其他信号槽,用于触发线程执行槽函数里的任务
connect(this, &MultiThreadDemo::StartCalculate, mCalculate, &Calculate::startCalculateSlot, Qt::QueuedConnection); // 默认使用Qt::QueuedConnection,保证槽函数的执行顺序
connect(mCalculate, &Calculate::CalculateFinished, this, &MultiThreadDemo::calculate_finished_slot, Qt::QueuedConnection); // 计算完成显示信息
connect(mCalculate, &Calculate::UpdateProssorbar, this, &MultiThreadDemo::update_prossorbar_slot, Qt::QueuedConnection); // 每计算一次完一次任务,更新界面进度条
mThread->start(); // 启动线程,线程默认开启事件循环,并且线程正处于事件循环状态
类似Run函数实现
计算类使用 qthreadpool 线程池实现,这里的任务是独立的,每个计算任务仅使用传递的参数,没有涉及资源共享
void Calculate::startCalculateSlot(const CalculateInputStruct aInput)
{
auto test_thread_id = QThread::currentThreadId(); // 查看当前线程的id和界面类、任务类的id是否相同
QThreadPool thread_pool;
thread_pool.setMaxThreadCount(aInput.threadMaxCount); // 设置线程池的最大线程数为1
for (int i = 0; i < aInput.calculateCount; i++)
{
CalculateRunnable* runnable = new CalculateRunnable;
runnable->setAutoDelete(true); // 设置任务结束后自动删除
thread_pool.start(runnable);
//! 设置响应方式为消息队列时,会在emit CalculateFinished(); 后再响应,故这里使用直接响应
connect(runnable, &CalculateRunnable::RunnableFinished, this, [&]() {emit UpdateProssorbar(); }, Qt::DirectConnection);
}
thread_pool.waitForDone(); // 等待所有任务完成
emit CalculateFinished();
}
}
结果对比
一个任务类在run函数中的计算为0.5s。可以看出,理论上线程池中还是有效的。
参考
在寻找答案的过程中,看了很多帖子,分享几遍个人觉得质量不错的文章
- Qt多线程通信
- Qt线程之QRunnable的使用详解
- QT 多线程的实现方法以及GUI线程与其他线程间的通信
- 【QT】子类化QObject+moveToThread实现多线程
- Qt使用多线程的一些心得——1.继承QThread的多线程使用方法