【QT常用技术讲解】多线程和线程池

前言

引入线程机制不一定能提高性能,小项目小程序大多情况下是用不上多线程的。引入多线程/线程池通常是为了处理"应用卡顿、反应慢"等问题。本篇主要分享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常用技术讲解】多线程处理+全局变量处理异步事件并获取多个线程返回的结果

【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 &param);//线程池提交带参数的函数时,不使用静态函数
};

#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 &param)
{
    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对多线程进行了特性封装,非常方便使用,只是一些封装的特性,没有什么技术难点,建议收藏本博文,有需要时,拷贝下来用即可。

相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
小bo波9 天前
使用Thread子类创建线程 VS 使用Runnable接口创建线程的区别
java·多线程·thread·并发编程·runnable
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能16 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构