Qt中多线程QThread、QThreadPool、QConCurrent三种方式对比

Qt中多线程QThread、QThreadPool、QConCurrent三种方式对比

第一层:QThread ------ 基石与底层控制(管理"线程")

QThread 是 Qt 多线程的基石,本质上是对操作系统原生线程的面向对象封装。

  • 核心思想:你直接创建和控制线程的生命周期。
  • 使用方式
    1. 继承重写法 :继承 QThread 重写 run() 函数。简单粗暴,但不够灵活,线程和任务耦合在一起。
    2. moveToThread法 :继承 QObject,通过 object->moveToThread(thread) 将对象移入线程。这是 Qt 官方推荐的写法,利用信号槽驱动任务执行,线程与任务解耦。
  • 优点:控制力最强。你可以精确控制线程的优先级、栈大小、启动、停止、甚至是线程内的事件循环。
  • 缺点 :开销大(线程创建/销毁系统开销大);管理难(如果随手 new QThread,容易导致线程数量失控);不适合海量短任务。
  • 适用场景常驻线程。比如后台一直运行的硬件通信轮询、服务器监听、需要独立事件循环处理信号槽的长生命周期任务。

代码示例:

  1. QThread (moveToThread 法) ------ 面向对象,事件循环驱动

这是 Qt 官方推荐的做法。我们将创建一个 Worker 类,将其移入 QThread,通过信号槽触发任务并返回结果。

核心特征:线程常驻、支持信号槽、拥有独立事件循环。

c++ 复制代码
#include <QCoreApplication>
#include <QThread>
#include <QObject>
#include <QDebug>

// 1. 定义工作类(千万不要继承QThread)
class Worker : public QObject {
    Q_OBJECT
public:
    explicit Worker(int id) : m_id(id) {}

public slots:
    // 槽函数:执行耗时任务
    void doWork() {
        qDebug() << "Worker" << m_id << "running in thread:" << QThread::currentThreadId();
        QThread::msleep(1000); // 模拟耗时操作
        int result = m_id * 100; // 模拟计算结果
        emit resultReady(m_id, result); // 发送结果信号
    }

signals:
    void resultReady(int id, int result);

private:
    int m_id;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    qDebug() << "Main thread:" << QThread::currentThreadId();

    QList<QThread*> threads;
    QList<Worker*> workers;

    // 创建 5 个工作对象和 5 个线程
    for (int i = 0; i < 5; ++i) {
        Worker *worker = new Worker(i);
        QThread *thread = new QThread();
        
        worker->moveToThread(thread); // 将工作对象移入新线程
        
        // 连接启动信号:线程启动后,调用 worker 的 doWork
        QObject::connect(thread, &QThread::started, worker, &Worker::doWork);
        // 连接结果信号:将结果打印到主线程
        QObject::connect(worker, &Worker::resultReady, [](int id, int result){
            qDebug() << "  -> Got result from Worker" << id << ":" << result;
        });
        // 连接清理信号:任务完成后,安全退出线程并清理内存
        QObject::connect(worker, &Worker::resultReady, thread, &QThread::quit);
        QObject::connect(thread, &QThread::finished, worker, &QObject::deleteLater);
        QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);

        threads.append(thread);
        workers.append(worker);
    }

    // 启动所有线程
    for (QThread *t : threads) {
        t->start();
    }

    return a.exec();
}

代码运行结果:

第二层:QThreadPool + QRunnable ------ 任务池化(管理"任务")

随着需求的复杂,你发现频繁创建销毁 QThread 太浪费资源,于是进阶到了线程池。QRunnable 是任务,QThreadPool 是执行任务的池子。

  • 核心思想:将"线程"和"任务"解耦。你只需要写任务,线程的创建、复用、销毁交给线程池。
  • 使用方式 :继承 QRunnable 重写 run(),然后丢给 QThreadPool::start()
  • 进阶体现
    1. 资源复用:避免了频繁创建/销毁线程的系统调用。
    2. 并发控制 :通过 maxThreadCount() 限制最大并发数,防止系统资源耗尽。
  • 缺点QRunnable 本身不是继承自 QObject不支持信号槽 (虽然可以手动使用 QMetaObject::invokeMethod 或组合 QObject 来解决,但终归麻烦);缺乏取消、进度汇报等高级功能。
  • 适用场景大量独立的、无状态的短任务。比如批量处理图片、批量下载文件、并发的数学计算。丢进池子,跑完即止。

代码示例:

  1. QThreadPool + QRunnable ------ 任务池化,拿来即走

我们将任务封装为 QRunnable,丢给全局线程池。因为 QRunnable 不是 QObject,无法直接使用信号槽,这里我们使用 C++11 的 std::function 回调来返回结果。

核心特征:线程复用、无事件循环、适合一次性短任务、需手动处理结果回传。

C++ 复制代码
#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

// 1. 定义任务类
class ComputeTask : public QRunnable {
public:
    ComputeTask(int id, std::function<void(int, int)> callback) 
        : m_id(id), m_callback(callback) 
    {
        // 设置任务执行完后自动销毁,防止内存泄漏
        setAutoDelete(true); 
    }

    // 重写 run 函数
    void run() override {
        qDebug() << "Task" << m_id << "running in thread:" << QThread::currentThreadId();
        QThread::msleep(1000); // 模拟耗时操作
        int result = m_id * 100;
        
        // QRunnable 没有信号槽,使用回调函数将结果传回
        if (m_callback) {
            m_callback(m_id, result);
        }
    }

private:
    int m_id;
    std::function<void(int, int)> m_callback;
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    qDebug() << "Main thread:" << QThread::currentThreadId();

    // 设置全局线程池最大线程数为 2(为了演示线程复用)
    QThreadPool::globalInstance()->setMaxThreadCount(2);

    for (int i = 0; i < 5; ++i) {
        // 定义回调函数接收结果
        auto callback = [](int id, int result) {
            qDebug() << "  -> Got result from Task" << id << ":" << result;
        };
        
        ComputeTask *task = new ComputeTask(i, callback);
        // 丢给线程池执行
        QThreadPool::globalInstance()->start(task);
    }

    // 等待所有任务完成再退出(仅为了演示,实际GUI程序不推荐主线程wait)
    QThreadPool::globalInstance()->waitForDone();
    qDebug() << "All tasks finished.";

    return 0; // 不需要 a.exec(),因为没有事件循环
}

代码运行结果:

第三层:QtConcurrent ------ 高层函数式并发(管理"结果")

当你要处理的不仅是任务,而是数据集合(比如一个 List 里的所有元素都要做相同操作),QRunnable 写起来还是很啰嗦(要写类、要重写run、要处理数据传递)。于是进阶到了 QtConcurrent

  • 核心思想 :声明式编程。你只需要告诉 Qt "对这堆数据执行什么函数",Qt 自动帮你分配到线程池(底层默认使用全局 QThreadPool)。

  • 使用方式QtConcurrent::map() / filter() / mappedReduced() / run()。配合 C++11 的 Lambda 表达式,代码极其精简。

  • 进阶体现

    1. 极简 API:一行代码即可实现多并发。
    2. QFuture 绑定 :返回 QFuture 对象,可以随时查询计算进度(progressValue)、挂起(suspend)、恢复(resume)、取消(cancel)以及获取最终结果。
    3. 自动负载均衡:根据 CPU 核心数自动切分数据块,实现最佳负载均衡。
  • 缺点 :灵活性最低。无法控制任务具体跑在哪个线程,无法像 QThread 那样运行独立的事件循环。

    适用场景数据并行处理 。比如对一个包含 10000 个元素的 QList 同时求平方、过滤出符合条件的对象等。

    代码示例:

    我们不再需要显式定义类,直接使用 QtConcurrent::run 将一个 Lambda 表达式丢进线程池,并利用 QFutureWatcher 异步监听结果。

    核心特征:代码极简、自动数据分片、QFuture 提供进度/取消/结果监听、无需关心线程。

    c++ 复制代码
    #include <QCoreApplication>
    #include <QtConcurrent>
    #include <QFutureWatcher>
    #include <QDebug>
    
    int main(int argc, char *argv[]) {
        QCoreApplication a(argc, argv);
        qDebug() << "Main thread:" << QThread::currentThreadId();
    
        // 同样限制全局线程池大小以便观察
        QThreadPool::globalInstance()->setMaxThreadCount(2);
    
        // 使用 QFutureWatcher 监听异步结果(因为 QtConcurrent 本身是非阻塞的)
        QFutureWatcher<int> *watcher = new QFutureWatcher<int>();
        
        // 当单个结果就绪时触发
        QObject::connect(watcher, &QFutureWatcher<int>::resultReadyAt, [](int index) {
            qDebug() << "  -> Result at index" << index << "is ready.";
        });
    
        // 当所有任务完成时触发
        QObject::connect(watcher, &QFutureWatcher<int>::finished, [watcher]() {
            qDebug() << "All concurrent tasks finished.";
            // 获取所有结果
            QFuture<int> future = watcher->future();
            for (int i = 0; i < future.resultCount(); ++i) {
                qDebug() << "    Final Result[" << i << "] =" << future.resultAt(i);
            }
            watcher->deleteLater();
            QCoreApplication::quit(); // 退出程序
        });
    
        // 使用 QtConcurrent::run 启动并发任务
        // 返回 QFuture 对象用于跟踪状态
        QFuture<int> future = QtConcurrent::mapped(QList<int>() << 0 << 1 << 2 << 3 << 4, [](int id) -> int {
            qDebug() << "Concurrent task" << id << "running in thread:" << QThread::currentThreadId();
            QThread::msleep(1000); // 模拟耗时
            return id * 100;       // 直接返回结果
        });
    
        // 将 future 交给 watcher 监控
        watcher->setFuture(future);
    
        return a.exec();
    }

代码运行结果:

四、对比小结

维度 QThread QThreadPool + QRunnable QtConcurrent
控制粒度 线程级(最强) 任务级 数据级(最弱)
开发效率 较低(需手动管理生命周期) 中等(需封装任务类) 极高(一行代码/Lambda)
事件循环 支持(核心优势) 不支持(默认无) 不支持
信号槽 完美支持 不原生支持 不支持(通过QFuture获取结果)
底层依赖 操作系统 API 依赖 QThread 依赖 QThreadPool
核心场景 常驻后台、需事件循环 大量独立短任务 数据集合的并行计算

从代码看差异

  1. 代码量与复杂度
    • QThread 代码最臃肿。你需要管理 QObjectQThread 的创建、moveToThread、信号槽连接,以及极其重要的内存清理逻辑finished -> deleteLater)。
    • QRunnable 适中。你需要继承并重写 run(),最关键的是要自己想办法把结果"传出来"(如回调、QMetaObject::invokeMethod),容易写出回调地狱。
    • QtConcurrent 最清爽。不用写类,不用管线程,一行 runmapped 搞定,配合 QFutureWatcher 获取结果非常优雅。
  2. 结果获取方式
    • QThread:通过信号槽,天然线程安全,完美融入 Qt 事件循环。
    • QRunnable:通过回调/std::function ,或者在 run() 里用 QMetaObject::invokeMethod 往主线程发信号,略显生硬。
    • QtConcurrent:通过 QFuture / QFutureWatcher,这是 Qt 提供的高层抽象,不仅能拿结果,还能监听进度、取消任务。
  3. 生命周期与线程复用(重点体会)
    • QThread 示例中,5 个任务创建了 5 个真实的系统线程,任务完成后线程退出销毁。
    • QRunnableQtConcurrent 示例中,我们设置了 setMaxThreadCount(2)。你会在运行输出中发现,5 个任务只创建了 2 个线程 !前 2 个任务跑完后,线程没死,接着跑后 2 个,实现了线程复用,大幅节省了系统资源。

选择建议:

  • 如果你需要后台一直跑(比如监听串口数据),用 QThread + moveToThread
  • 如果你有一堆零碎的独立任务(比如批量解析 100 个文件),用 QThreadPool + QRunnable
  • 如果你只是想让一段计算代码在后台跑别卡界面,或者要对一个列表里的数据并行处理,毫不犹豫选 QtConcurrent
相关推荐
小短腿的代码世界7 小时前
Qt Bluetooth源码深度解析:从HCI协议到跨平台API的完整架构
开发语言·qt·架构
小短腿的代码世界8 小时前
Qt WebEngine多进程架构与IPC通信:从Chromium多进程到Qt信号槽融合
qt·架构·系统架构
尘中远10 小时前
【Qwt 7.0 系列】快速入门与核心新特性概览 —— 更现代的 Qt 数据可视化库
qt·数据可视化·绘图·qwt·科学绘图
初阳78512 小时前
【Qt】系统相关(3)——多线程
qt
初阳78515 小时前
【Qt】系统相关(2)——文件
开发语言·qt
尘中远1 天前
【Qwt 7.0 系列】坐标轴与刻度系统 —— 刻度引擎、网格、图例与刻度朝内
qt·数据可视化·qcustomplot·qwt·工业软件·科学绘图
sycmancia1 天前
Qt——多线程间的互斥
开发语言·qt
尘中远1 天前
【Qwt 7.0 系列】常用图表类型实战 —— 柱状图、散点图、箱线图与直方图
qt·qwt·工业软件·科学绘图
尘中远1 天前
【Qwt 7.0 系列】交互功能详解 —— 平移、缩放、坐标轴交互与数据拾取
qt·数据可视化·绘图·qcustomplot·qwt·科学绘图
sycmancia2 天前
Qt——进程与线程的概念
qt