前言
引入线程机制不一定能提高性能,小项目小程序大多情况下是用不上多线程的。引入多线程/线程池通常是为了处理"应用卡顿、反应慢"等问题。本篇主要分享QT C++特性的多线程和线程池(QT做了封装)的调用。
主要应用场景
(1)阻塞操作
有些任务执行是需要花费时间的(超过1秒),比如在开发摄像头应用时,需要获取摄像头名称、分辨率、支持的格式、FPS等信息,通常要花费1~2秒,超过1秒,可以感知到应用窗口是卡顿的。
如果只是开启应用的时候仅获取一次,没必要专门写一个多线程来执行,通常开启应用在2~3秒内更新相应的信息,是可接受的,并且开启/关闭/线程、回收线程资源都涉及到(用户层<-->内核层)资源窗口切换,只会更浪费时间。
如果是应用界面提供一个常用的【信息刷新】按钮功能,用户一点此按钮就导致应用窗口卡顿,这是很不好的体验,这种常态化功能,是有必要使用多线程来实现异步处理的。
(2)长时间阻塞
单任务比较耗时的,比如大文件读写、网络请求、消息队列等待、复杂计算(比如图片处理),这会明显导致应用窗口卡顿。
单任务需要循环执行,比如在应用中设计了"自动识别新插入的USB摄像头"模块,需要每隔几秒获取摄像头信息来判断是否有新的USB摄像头,这也会导致应用窗口一卡一顿的。(可以延伸到后台服务、定时监控的场景)
单任务且涉及大量的IO操作,比如处理大量的小文件、作为网络服务器端接收网络请求等,也会导致应用窗口长时间卡顿。
以上两个场景都应该使用多线程/线程池进行异步处理,提供信号-槽机制,与主框架界面进行交互。
**(3)**解耦出异步交互模块
(2)描述的比较笼统,有些应用,处理的数据比较少时,遇不上瓶颈。但是拿到客户现场试运行时,发现因为需要处理的数据大增导致应用特别卡顿,此时就需要对某些耗时多的功能模块进行解耦,并设计与承接模块之间进行异步交互的模式.。
功能讲解
以下仅讲解QT封装好的多线程和线程池。
1、多线程
有两种实现方式:
- 继承QThread类:重写run()方法来定义线程执行的任务;
- moveToThread:将QObject对象移动到独立线程中执行。
之前分享的博文是继承QThread类方式,以下是导读:
【QT常用技术讲解】多线程处理+全局变量处理异步事件并获取多个线程返回的结果
【QT常用技术讲解】多线程执行后台命令行的两种方式(后台运行和返回打印信息)
项目结构说明
- CameraProbing类:实现获取电脑上的USB摄像头信息
- CameraWorker类:负责启动多进程(包含2种moveToThread方式)
- MainWindow窗口类:与多线程进行信号-槽交互
CameraProbing类的实现
//头文件cameraprobing.h
#ifndef CAMERAPROBING_H
#define CAMERAPROBING_H
#include <QObject>
#include <QString>
#include <QSize>
#include <QList>
#include <QProcess>
#include <QTextStream>
#include <QRegularExpression>
#include <QDebug>
class CameraProbing : public QObject
{
Q_OBJECT
public:
explicit CameraProbing(QObject *parent = nullptr);
static QList<QString> getAvailableCameras();
};
#endif // CAMERAPROBING_H
//cameraprobing.cpp
CameraProbing::CameraProbing(QObject *parent) : QObject(parent)
{
}
QList<QString> CameraProbing::getAvailableCameras()
{
QList<QString> cameras;
QStringList arguments;
arguments << "-f" << "dshow" << "-list_devices" << "true" << "-i" << "dummy";
// 使用静态函数调用
QString output = executeFFmpegCommand(arguments);
if (output.isEmpty()) {
qDebug() << "获取摄像头列表失败";
return cameras;
}
//qDebug() << "摄像头列表原始输出:\n" << output;
// 解析摄像头列表
QTextStream stream(const_cast<QString*>(&output), QIODevice::ReadOnly);
QString line;
// 改进的正则表达式,匹配FFmpeg dshow输出的格式
QRegularExpression cameraPattern(R"(\]\s+\"([^\"]+)\"\s+\(video\))");
QRegularExpression cameraPattern2(R"(\"([^\"]+)\"\s+\(video\))");
while (stream.readLineInto(&line)) {
QRegularExpressionMatch match = cameraPattern.match(line);
if (!match.hasMatch()) {
match = cameraPattern2.match(line);
}
if (match.hasMatch()) {
QString cameraName = match.captured(1);
cameras.append(cameraName);
qDebug() << "发现摄像头:" << cameraName;
}
}
return cameras;
}
CameraWorker类
// cameraworker.h
#ifndef CAMERAWORKER_H
#define CAMERAWORKER_H
#include <QObject>
#include "cameraprobing.h"
class CameraWorker : public QObject
{
Q_OBJECT
public:
explicit CameraWorker(QObject *parent = nullptr);
~CameraWorker();
public slots:
void startDetection();
signals:
void camerasDetected(const QStringList &cameras);//参数使用QT的内置类
//void camerasDetected(const QList<QString> &cameras);//参数使用自定义类
void detectionFailed(const QString &error);
private:
CameraProbing m_probing;
};
#endif // CAMERAWORKER_H
// cameraworker.cpp
#include "cameraworker.h"
#include <QtConcurrent>
#include <QDebug>
CameraWorker::CameraWorker(QObject *parent) : QObject(parent)
{
}
CameraWorker::~CameraWorker()
{
// 等待任务完成
if (m_watcher.isRunning()) {
m_watcher.waitForFinished();
}
}
void CameraWorker::startDetection()
{
qDebug() << "在后台线程中开始摄像头检测...";
try {
// 调用静态方法获取摄像头列表
QStringList cameras = CameraProbing::getAvailableCameras();
if (cameras.isEmpty()) {
emit detectionFailed("未检测到任何摄像头设备");
} else {
emit camerasDetected(cameras);
}
} catch (const std::exception &e) {
emit detectionFailed(QString("摄像头检测异常: %1").arg(e.what()));
}
}
注意,以上线程发送的信号函数camerasDetected带有参数,如果参数是自定义类型时(比如QList<QString>),必须在main.cpp的main()中进行元类型注册(提这一点的目的是,需要传递的信息多时,通常用QList<自定义结构体>,此时也需要进行原类型注册)
qRegisterMetaType<QList<QString>>("QList<QString>");
不然会报如下错误:
QObject::connect: Cannot queue arguments of type 'QList<QString>'
(Make sure 'QList<QString>' is registered using qRegisterMetaType().)
MainWindow窗口类
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QList>
#include <QThread>
#include <QMessageBox>
#include "cameraprobing.h"
#include "cameraworker.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onCamerasDetected(const QList<QString> &cameras);
void onDetectionFailed(const QString &error);
void startCameraDetection();
private:
Ui::MainWindow *ui;
void setupThreadmove1();
void setupThreadmove2();
QThread *m_cameraThread;
CameraWorker *m_cameraWorker;
};
#endif // MAINWINDOW_H
//mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setupThreadmove1();
//setupThreadmove2();
}
MainWindow::~MainWindow()
{
delete ui;
if (m_cameraThread && m_cameraThread->isRunning()) {
// 请求线程退出
m_cameraThread->quit();
// 等待线程完全结束(最多3秒)
if (!m_cameraThread->wait(3000)) {
qWarning() << "线程无法正常退出,强制终止";
m_cameraThread->terminate();
m_cameraThread->wait();
}
}
// 删除worker对象(已在后台线程中)
m_cameraWorker->deleteLater();
}
void MainWindow::setupThreadmove1()
{
// 创建后台线程
m_cameraThread = new QThread(this);
//创建worker对象
m_cameraWorker = new CameraWorker();
// 将worker对象移至后台线程
m_cameraWorker->moveToThread(m_cameraThread);
// 连接信号槽
connect(m_cameraWorker, &CameraWorker::camerasDetected,
this, &MainWindow::onCamerasDetected);
connect(m_cameraWorker, &CameraWorker::detectionFailed,
this, &MainWindow::onDetectionFailed);
// 启动线程
m_cameraThread->start();
startCameraDetection();
}
void MainWindow::startCameraDetection()
{
// 通过信号触发后台任务
QMetaObject::invokeMethod(m_cameraWorker, "startDetection");
}
void MainWindow::setupThreadmove2()
{
// 创建后台线程
m_cameraThread = new QThread(this);
//创建worker对象
m_cameraWorker = new CameraWorker();
// 将worker对象移至后台线程
m_cameraWorker->moveToThread(m_cameraThread);
// 连接信号槽
//接收线程启动信号,并触发槽函数CameraWorker::startDet
connect(m_cameraThread, &QThread::started, m_cameraWorker, &CameraWorker::startDetection);
//接收线程发送的信号,触发正常/异常检查结果
connect(m_cameraWorker, &CameraWorker::camerasDetected,
this, &MainWindow::onCamerasDetected);
connect(m_cameraWorker, &CameraWorker::detectionFailed,
this, &MainWindow::onDetectionFailed);
// 启动线程
m_cameraThread->start();
}
void MainWindow::onCamerasDetected(const QList<QString> &cameras)
{
qDebug() << "在主线程中接收到摄像头列表,数量:" << cameras.size();
// 安全地处理结果(在GUI线程中)
for (const QString &camera : cameras) {
qDebug() << "摄像头:" << camera;
}
}
void MainWindow::onDetectionFailed(const QString &error)
{
qDebug() << "摄像头检测失败:" << error;
// 显示错误信息
QMessageBox::warning(this, "摄像头检测", error);
}
moveToThread方式一
关键点:
①创建线程和线程任务对象
②将线程任务对象移到线程中:Worker->moveToThread(Thread)
③设置信号-槽机制,接收线程任务的camerasDetected和detectionFailed信号:connect
④启动线程:Thread->start()
⑤通过Qt的事件系统进行跨线程安全调用:QMetaObject::invokeMethod
void MainWindow::setupThreadmove1()
{
// 创建后台线程
m_cameraThread = new QThread(this);
//创建worker对象
m_cameraWorker = new CameraWorker();
// 将worker对象移至后台线程
m_cameraWorker->moveToThread(m_cameraThread);
// 连接信号槽
connect(m_cameraWorker, &CameraWorker::camerasDetected,
this, &MainWindow::onCamerasDetected);
connect(m_cameraWorker, &CameraWorker::detectionFailed,
this, &MainWindow::onDetectionFailed);
// 启动线程
m_cameraThread->start();
startCameraDetection();
}
void MainWindow::startCameraDetection()
{
// 通过信号触发后台任务
QMetaObject::invokeMethod(m_cameraWorker, "startDetection");
}
moveToThread方式二
关键点:
①创建线程和线程任务对象
②将线程任务对象移到线程中:Worker->moveToThread(Thread)
③设置信号-槽机制,接收QThread::started,以及线程任务的camerasDetected和detectionFailed信号:connect
④启动线程:Thread->start()
void MainWindow::setupThreadmove2()
{
// 创建后台线程
m_cameraThread = new QThread(this);
//创建worker对象
m_cameraWorker = new CameraWorker();
// 将worker对象移至后台线程
m_cameraWorker->moveToThread(m_cameraThread);
// 连接信号槽
//接收线程启动信号,并触发槽函数CameraWorker::startDet
connect(m_cameraThread, &QThread::started, m_cameraWorker, &CameraWorker::startDetection);
//接收线程发送的信号,触发正常/异常检查结果
connect(m_cameraWorker, &CameraWorker::camerasDetected,
this, &MainWindow::onCamerasDetected);
connect(m_cameraWorker, &CameraWorker::detectionFailed,
this, &MainWindow::onDetectionFailed);
// 启动线程
m_cameraThread->start();
}
方法二比较简单,调用Tread->start()时,就会触发QThread::started信号。
2、线程池
需要在项目.pro文件中加入concurrent
QT += concurrent
关键点:
①包含必要的头文件:
#include <QtConcurrent> // QtConcurrent核心功能
#include <QFuture> // 异步计算结果
#include <QFutureWatcher> // Future状态监控
#include <QThreadPool> // 线程池管理(可选)
②定义要在线程池中执行的任务:即希望线程池执行的任务
③提交任务到任务池:通过QtConcurrent::run执行
④启动任务监控:QFutureWatche.setFuture(QFuture)
⑤设置信号-槽机制,接收QFutureWatcher::finished信号,处理任务完成事件
以上特别需要注意的信号-槽机制点: 多线程方式,执行的任务函数,可以发送任意的信号,任务灵活性强,而线程池只能接收QFutureWatcher::finished信号,即处理收尾事件,这个机制的区别,也注定了线程池适合处理短时间、简易计算的场景,而多线程适合处理长时间运行,存在业务优先级/动作等较为复杂的场景。
以下为源码:
// cameraworker.h
#ifndef CAMERAWORKER_H
#define CAMERAWORKER_H
#include <QObject>
#include "cameraprobing.h"
//线程池方式的头文件
#include <QFuture>
#include <QFutureWatcher>
class CameraWorker : public QObject
{
Q_OBJECT
public:
explicit CameraWorker(QObject *parent = nullptr);
~CameraWorker();
public slots:
void startDetection();//多线程执行的任务
void startDetectionbypool();//线程池执行的任务
signals:
void camerasDetected(const QList<QString> &cameras);
void detectionFailed(const QString &error);
private slots:
void onDetectionFinished();//线程池处理任务完成事件
private:
CameraProbing m_probing;
QFuture<QList<QString>> m_future;
QFutureWatcher<QList<QString>> m_watcher;
static QList<QString> performDetection1();//线程池提交空参数的函数时,使用静态函数
QList<QString> performDetection2(const QString ¶m);//线程池提交带参数的函数时,不使用静态函数
};
#endif // CAMERAWORKER_H
// cameraworker.cpp
#include "cameraworker.h"
#include <QtConcurrent>
#include <QDebug>
CameraWorker::CameraWorker(QObject *parent) : QObject(parent)
{
}
CameraWorker::~CameraWorker()
{
// 等待任务完成
if (m_watcher.isRunning()) {
m_watcher.waitForFinished();
}
}
void CameraWorker::startDetection()
{
qDebug() << "在后台线程中开始摄像头检测...";
try {
// 调用静态方法获取摄像头列表
QList<QString> cameras = CameraProbing::getAvailableCameras();
if (cameras.isEmpty()) {
emit detectionFailed("未检测到任何摄像头设备");
} else {
emit camerasDetected(cameras);
}
} catch (const std::exception &e) {
emit detectionFailed(QString("摄像头检测异常: %1").arg(e.what()));
}
}
void CameraWorker::startDetectionbypool()
{
{
QThreadPool* pool = QThreadPool::globalInstance();
qDebug() << "最大线程数:" << pool->maxThreadCount();
qDebug() << "活跃线程数:" << pool->activeThreadCount();
}
qDebug() << "使用QtConcurrent线程池开始摄像头检测...";
// 如果已有任务在运行,先取消
if (m_watcher.isRunning()) {
m_watcher.cancel();
m_watcher.waitForFinished();
}
// 使用QtConcurrent在线程池中运行检测任务
//m_future = QtConcurrent::run(performDetection1);
m_future = QtConcurrent::run(this,&CameraWorker::performDetection2, QString("asd"));
m_watcher.setFuture(m_future); // 设置FutureWatcher监控这个任务
// 连接FutureWatcher的信号
connect(&m_watcher, &QFutureWatcher<QList<QString>>::finished,
this, &CameraWorker::onDetectionFinished);
}
QList<QString> CameraWorker::performDetection1()
{
qDebug() << "在QtConcurrent线程池中执行摄像头检测...";
try {
// 调用静态方法获取摄像头列表
QList<QString> cameras = CameraProbing::getAvailableCameras();
return cameras;
} catch (const std::exception &e) {
// 抛出异常,在onDetectionFinished中处理
throw e;
}
}
QList<QString> CameraWorker::performDetection2(const QString ¶m)
{
qDebug() << "在QtConcurrent线程池中执行摄像头检测...";
try {
// 调用静态方法获取摄像头列表
QList<QString> cameras = CameraProbing::getAvailableCameras();
return cameras;
} catch (const std::exception &e) {
// 抛出异常,在onDetectionFinished中处理
throw e;
}
}
void CameraWorker::onDetectionFinished()
{
try {
// 获取检测结果
QList<QString> cameras = m_watcher.result();
if (cameras.isEmpty()) {
emit detectionFailed("未检测到任何摄像头设备");
} else {
emit camerasDetected(cameras);
}
} catch (const std::exception &e) {
emit detectionFailed(QString("摄像头检测异常: %1").arg(e.what()));
}
}
以上提供了两种调用QtConcurrent::run提交任务函数到任务池:①任务函数不包含参数时,任务函数需要声明为静态函数;②任务函数包含参数时,任务函数需要声明为普通成员函数,静态函数会报错。
在mainwindow类中增加一个线程池执行函数setupThreadpool
void MainWindow::setupThreadpool()
{
m_cameraWorker = new CameraWorker();
// 直接连接信号槽,不需要管理线程
connect(m_cameraWorker, &CameraWorker::camerasDetected,
this, &MainWindow::onCamerasDetected);
connect(m_cameraWorker, &CameraWorker::detectionFailed,
this, &MainWindow::onDetectionFailed);
m_cameraWorker->startDetectionbypool();
}
多线程和线程池差异
| 特性 | 多线程(QThread) | 线程池(QtConcurrent) |
|---|---|---|
| 线程创建方式 | 手动创建和管理线程对象 | 自动从线程池获取空闲线程 |
| 资源开销 | 高 - 频繁创建销毁线程消耗大 | 低 - 线程复用,减少创建销毁开销 |
| 线程数量控制 | 需要手动控制,容易创建过多线程 | 自动控制,有最大线程数限制 |
| 任务调度 | 需要自行实现任务队列和调度 | 自动调度,任务排队执行 |
| 内存使用 | 每个线程都有独立的栈空间 | 线程复用,内存使用更高效 |
| 响应速度 | 线程创建有延迟 | 立即执行(有可用线程时) |
| 适用场景 | 长时间运行的任务、需要精确控制的任务 | 短时间任务、大量并行任务、IO密集型任务 |
| 复杂度 | 高 - 需要管理线程生命周期 | 低 - 自动管理,使用简单 |
| 错误处理 | 需要在线程内处理异常 | 可通过Future统一处理异常 |
| 任务优先级 | 可自定义优先级调度 | 通常先进先出,优先级支持有限 |
篇尾
QT对多线程进行了特性封装,非常方便使用,只是一些封装的特性,没有什么技术难点,建议收藏本博文,有需要时,拷贝下来用即可。