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

相关推荐
宠..30 分钟前
创建文本框控件
linux·运维·服务器·开发语言·qt
透明的玻璃杯3 小时前
VS2015 调用QT5.9.9 的库文件 需要设置QT库的路径
开发语言·qt
feiyangqingyun3 小时前
Qt/C++地图最简示例/在线离线切换/地图视图切换/执行各种js函数交互
javascript·c++·qt
翻斗花园牛图图-3 小时前
Qt开发——系统相关3(Qt网络编程)
开发语言·qt
开始了码5 小时前
Qt:: 事件过滤器实战:图片点击缩放交互实现(含 QMatrix 详解)
qt
秦jh_5 小时前
【Qt】Qt 概述
开发语言·qt
韭菜钟18 小时前
在Qt中使用QuickJS
开发语言·qt
枫叶丹418 小时前
【Qt开发】Qt窗口(三) -> QStatusBar状态栏
c语言·开发语言·数据库·c++·qt·microsoft
t***316521 小时前
QT开发:事件循环与处理机制的概念和流程概括性总结
开发语言·qt