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

 更新时间:2026年05月25日 09:57:56   作者:追烽少年x  
QThread是Qt多线程的基石,本质上是对操作系统原生线程的面向对象封装,本文介绍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,通过信号槽触发任务并返回结果。

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

#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 回调来返回结果。

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

#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 表达式,代码极其精简。
  • 进阶体现
    • 极简 API:一行代码即可实现多并发。
    • QFuture 绑定:返回 QFuture 对象,可以随时查询计算进度(progressValue)、挂起(suspend)、恢复(resume)、取消(cancel)以及获取最终结果。
    • 自动负载均衡:根据 CPU 核心数自动切分数据块,实现最佳负载均衡。
    • 缺点:灵活性最低。无法控制任务具体跑在哪个线程,无法像 QThread 那样运行独立的事件循环。
    • 适用场景数据并行处理。比如对一个包含 10000 个元素的 QList 同时求平方、过滤出符合条件的对象等。
  • 代码示例:
    • 我们不再需要显式定义类,直接使用 QtConcurrent::run 将一个 Lambda 表达式丢进线程池,并利用 QFutureWatcher 异步监听结果。
    • 核心特征:代码极简、自动数据分片、QFuture 提供进度/取消/结果监听、无需关心线程。
#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();
}

代码运行结果:

四、对比小结

维度QThreadQThreadPool + QRunnableQtConcurrent
控制粒度线程级(最强)任务级数据级(最弱)
开发效率较低(需手动管理生命周期)中等(需封装任务类)极高(一行代码/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

到此这篇关于Qt中多线程QThread、QThreadPool、QConCurrent三种方式对比分析的文章就介绍到这了,更多相关Qt 多线程QThread、QThreadPool、QConCurrent区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言动态开辟内存详解

    C语言动态开辟内存详解

    这篇文章主要为大家详细介绍了C语言动态开辟内存,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • C语言中void类型指针的实现

    C语言中void类型指针的实现

    C语言中void类型指针有着特殊的用途,本文就来一下void类型指针的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-06-06
  • C++归并排序算法详解

    C++归并排序算法详解

    大家好,本篇文章主要讲的是C++归并排序算法详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2022-01-01
  • 基于linux下获取时间函数的详解

    基于linux下获取时间函数的详解

    本篇文章是对linux下获取时间的函数进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C语言深入刨析数据结构之栈与链栈的设计与应用

    C语言深入刨析数据结构之栈与链栈的设计与应用

    栈是限定仅在表尾进行插入或删除操作的线性表,表尾称为栈顶(top),表头称为栈底(bottom)。栈的最主要特点就是“先进后出”(FILO),或“后进先出”(LIFO)。用链式存储结构表示的栈称为“链栈”,链栈对应于链表
    2022-05-05
  • C语言单链表的图文示例讲解

    C语言单链表的图文示例讲解

    单链表是链表的其中一种基本结构。一个最简单的结点结构如图所示,它是构成单链表的基本结点结构。在结点中数据域用来存储数据元素,指针域用于指向下一个具有相同结构的结点。 因为只有一个指针结点,称为单链表
    2023-02-02
  • C语言编程银行ATM存取款系统实现源码

    C语言编程银行ATM存取款系统实现源码

    这篇文章主要为大家介绍了C语言编程银行ATM存取款系统实现的源码示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-11-11
  • 浅谈C++中const与constexpr的区别

    浅谈C++中const与constexpr的区别

    C++11中新增加了用于指示常量表达式的constexpr关键字。本文将带大家详细了解一下const与constexpr之间的区别,需要的小伙伴们可以参考一下
    2021-11-11
  • Qt学习之容器的使用详解

    Qt学习之容器的使用详解

    Qt容器主要优点就是在所有的平台上的运行都表现的一致,并且它们都是隐含共享的,这篇文章就来和大家讲讲Qt中容器的具体用法吧,希望对大家有所帮助
    2023-03-03
  • 二叉树入门和刷题详解

    二叉树入门和刷题详解

    这篇文章主要介绍了二叉树入门和刷题详解的相关资料,需要的朋友可以参考下
    2023-07-07

最新评论