【QT】【C++】基于QT的多线程分别管理GUI和运算任务

一、简介

在 QT 应用开发中,多线程分离 GUI 与运算任务是解决 "耗时运算导致界面卡顿" 的核心架构设计,其核心思想是通过 QT 的多线程机制,将 "用户交互相关的 GUI 渲染" 与 "CPU 密集型 / IO 密集型运算任务" 分配到不同线程执行,既保证界面响应流畅,又提升运算效率。

1.1 核心设计背景

QT 的 GUI 线程(主线程)是单线程模型:所有界面渲染、事件处理(如点击、输入)均在主线程中执行。若直接在主线程中运行耗时运算(如大数据分析、复杂计算、网络下载、文件解析),会阻塞主线程的事件循环,导致界面冻结、无响应,严重影响用户体验。

通过多线程拆分后,实现 "分工明确":

  • GUI 线程(主线程):仅负责界面组件绘制、用户交互响应(按钮点击、输入处理)、界面状态更新(如进度条刷新、结果展示);
  • 运算线程(工作线程):独立执行耗时运算任务,不参与任何 GUI 操作,运算完成后通过 QT 安全机制通知 GUI 线程更新结果。

1.2 核心技术支撑

1. 多线程实现方式(QT 常用)

  • QThread 类 (经典方案):自定义工作线程继承 QThread,重写run()方法封装运算逻辑,通过start()启动线程(底层调用系统原生线程);
  • Qt Concurrent(简化方案) :基于高阶函数(如QtConcurrent::run()),无需手动管理线程生命周期,QT 自动维护线程池,适合简单耗时任务;
  • QRunnable+QThreadPool(池化方案):将运算任务封装为 QRunnable 子类,提交到 QThreadPool 线程池执行,适合批量、短期任务,减少线程创建销毁开销。

2. 线程间安全通信(关键)

QT 禁止工作线程直接操作 GUI 控件(跨线程操作 GUI 会导致崩溃或未知行为),需通过以下安全机制实现线程间数据交互:

  • 信号槽机制 (推荐):QT 的信号槽支持跨线程通信(默认通过Qt::QueuedConnection队列模式,避免线程竞争),工作线程通过发射信号传递运算结果,GUI 线程通过槽函数接收并更新界面;
  • QMetaObject::invokeMethod:动态调用 GUI 线程的成员函数,强制在 GUI 线程上下文执行,适合临时、简单的界面更新;
  • 线程安全数据结构 (辅助):如QQueue配合QMutex/QWaitCondition,实现线程间数据缓存与同步(适合高频数据交互场景)。

1.3 架构优势

  1. 界面响应流畅:GUI 线程不被耗时运算阻塞,用户点击、拖拽等操作实时反馈;
  2. 运算效率提升:耗时任务在独立线程执行,充分利用 CPU 多核资源,避免单线程瓶颈;
  3. 代码结构清晰:GUI 交互逻辑与运算业务逻辑分离,便于维护和扩展;
  4. QT 原生支持:无需依赖第三方库,QT 提供的多线程类和通信机制兼顾安全性与易用性。

1.4 典型适用场景

  • 数据可视化应用(如实时数据分析、图表渲染,运算线程处理数据,GUI 线程更新图表);
  • 后台任务处理(如文件批量转换、网络数据同步、数据库批量查询,运算线程执行任务,GUI 线程显示进度);
  • 实时监控系统(如传感器数据采集与分析,运算线程处理采集数据,GUI 线程实时展示监控状态);
  • 复杂计算应用(如科学计算、模拟仿真,运算线程执行核心算法,GUI 线程提供参数配置与结果展示)。

二、使用QObject::moveToThread()方法的代码示例

通过将QObject(或其子类)的实例移动到新线程中,可以在该线程中执行该对象的方法。这种方法更加灵活,不需要继承QThread类。

而另外的一种重写继承了QTThread的我们的任务类再去重写run(),这种方法不适合子任务繁杂且数量多的情况,如果都像继承了QTThread的任务都单独开一条线程做任务,你的多核芯片也是有极限的。

不如使用QObject::moveToThread()方法,使用信号触发对应的任务,这种还可以实例化多个QTThread的对象,其中一个QTThread的对象使用moveToThread()管理多个任务,同时还可以使用线程池QTThreadPool进一步减小开销。

2.1 创建工作对象:定义一个继承自QObject的类,并在其中定义需要在新线程中执行的方法(槽函数)。

mythread.h

cpp 复制代码
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>
#include <QVector>

class Generate : public QObject
{
    Q_OBJECT
public:
    explicit Generate(QObject *parent = nullptr);

    void working(int num);

signals:
    void sendArray(QVector<int> num);


};

class BubbleSort : public QObject
{
    Q_OBJECT
public:
    explicit BubbleSort(QObject *parent = nullptr);

    void working(QVector<int> list);

signals:
    void finish(QVector<int> num);


};

class QuickSort : public QObject
{
    Q_OBJECT
public:
    explicit QuickSort(QObject *parent = nullptr);

    void working(QVector<int> list);

private:
    void quickSort(QVector<int> &list,int l,int r);

signals:
    void finish(QVector<int> num);

};

#endif // MYTHREAD_H

mythread.c

cpp 复制代码
#include "mythread.h"
#include <QElapsedTimer>
#include <QDebug>
#include <QTime>
#include <QThread>

Generate::Generate(QObject *parent)
    : QObject{parent}
{}



void Generate::working(int num)
{
    qDebug()<< "生成随机数的线程地址" << QThread::currentThread();
    QElapsedTimer time;
    QVector<int> list;
    time.start();

    // 初始化随机数种子(基于当前时间)
    qsrand(QTime::currentTime().msecsSinceStartOfDay());

    for(int i = 0; i < num; ++i)
    {
        list.push_back(qrand() % 10000);  // 生成0-9999的随机数
    }
    int mlisec = time.elapsed();
    qDebug() << "生成数量:" << num << "耗时:" << mlisec << " ms";
    emit sendArray(list);
}

BubbleSort::BubbleSort(QObject *parent) : QObject{parent}
{

}


void BubbleSort::working(QVector<int> list)
{
    qDebug()<< "冒泡排序的线程的线程地址" << QThread::currentThread();
    QElapsedTimer time;

    time.start();
    int temp;
    for(int i = 0; i < list.size(); i++)
    {
        for(int j = 0; j < (list.size()-i-1); j++)
        {
            if(list[j] > list[j+1])
            {
                temp = list[j];
                list[j] = list[j+1];  // 修复:将前一个元素赋值为后一个元素
                list[j+1] = temp;       // 修复:将后一个元素赋值为临时变量(原前一个元素的值)
            }
        }
    }
    int mlisec = time.elapsed();
    qDebug() << "BubbleSort TIME:" << mlisec << " ms";
    emit finish(list);
}



QuickSort::QuickSort(QObject *parent) : QObject{parent}
{

}


void QuickSort::quickSort(QVector<int> &s, int l, int r)
{
    if (l < r)
    {
        int i = l, j = r;
        int x = s[l];  // 基准值
        while (i < j)
        {
            // 从右向左找小于x的数
            while (i < j && s[j] >= x)
                j--;
            if (i < j)
                s[i++] = s[j];  // 填入i位置,i右移

            // 从左向右找大于等于x的数
            while (i < j && s[i] < x)
                i++;
            if (i < j)
                s[j--] = s[i];  // 修复此处错误,原代码为s[j--] = s[j]
        }
        s[i] = x;  // 基准值归位
        quickSort(s, l, i - 1);
        quickSort(s, i + 1, r);
    }
}

void QuickSort::working(QVector<int> list)
{
    qDebug()<< "快速排序的线程的线程地址" << QThread::currentThread();
    QElapsedTimer time;

    time.start();
    quickSort(list, 0, list.size()-1);
    int mlisec = time.elapsed();
    qDebug() << "QuickSort TIME:" << mlisec << " ms";
    emit finish(list);
}

2.2 创建线程对象:在主线程中创建一个QThread实例。

mainwindow.h

cpp 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "mythread.h"

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

signals:
    void starting(int num);

private:
    Ui::MainWindow *ui;

};
#endif // MAINWINDOW_H

mainwindow.cpp

cpp 复制代码
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "mythread.h"

#include <QThread>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 创建子线程对象
    QThread* t1 = new QThread;
    QThread* t2 = new QThread;
    QThread* t3 = new QThread;

    // 创建继承了QObeject的任务类对象
    Generate* gen = new Generate;
    BubbleSort* bubble = new BubbleSort;
    QuickSort* quick = new QuickSort;

    // 将任务类对象移送到子线程
    gen->moveToThread(t1);
    bubble->moveToThread(t2);
    quick->moveToThread(t3);

    // 准备让子线程接收数据
    connect(this, &MainWindow::starting, gen, &Generate::working);

    // 启动子线程
    connect(ui->pushButton, &QPushButton::clicked, this, [=]()
    {
        emit starting(10000);
        t1->start();
    });

    connect(gen, &Generate::sendArray, bubble, &BubbleSort::working);
    connect(gen, &Generate::sendArray, quick, &QuickSort::working);


    // 准备显示来自子线程的数据
    // 修改生成随机数后的显示逻辑
    connect(gen, &Generate::sendArray, this, [=](QVector<int> list){
        t2->start();
        t3->start();

        // 优化随机数列表显示
        ui->randList->setUpdatesEnabled(false);  // 暂停UI更新提升效率
        ui->randList->clear();
        int showCount = qMin(100, list.size());  // 最多显示100个
        for(int i=0; i < showCount; ++i)
        {
            ui->randList->addItem(QString::number(list.at(i)));
        }
        if(list.size() > 100)
        {
            ui->randList->addItem(QString("... 共%1个元素,省略显示剩余%2个").arg(list.size()).arg(list.size()-100));
        }
        ui->randList->setUpdatesEnabled(true);  // 恢复UI更新
    });

    // 修改冒泡排序结果显示
    connect(bubble, &BubbleSort::finish, this, [=](QVector<int> list){
        ui->bubbleList->setUpdatesEnabled(false);
        ui->bubbleList->clear();
        int showCount = qMin(100, list.size());
        for(int i=0; i < showCount; ++i)
        {
            ui->bubbleList->addItem(QString::number(list.at(i)));
        }
        if(list.size() > 100)
        {
            ui->bubbleList->addItem(QString("... 共%1个元素,省略显示剩余%2个").arg(list.size()).arg(list.size()-100));
        }
        ui->bubbleList->setUpdatesEnabled(true);
    });

    // 修改快速排序结果显示
    connect(quick, &QuickSort::finish, this, [=](QVector<int> list){
        ui->quickList->setUpdatesEnabled(false);
        ui->quickList->clear();
        int showCount = qMin(100, list.size());
        for(int i=0; i < showCount; ++i)
        {
            ui->quickList->addItem(QString::number(list.at(i)));
        }
        if(list.size() > 100)
        {
            ui->quickList->addItem(QString("... 共%1个元素,省略显示剩余%2个").arg(list.size()).arg(list.size()-100));
        }
        ui->quickList->setUpdatesEnabled(true);
    });
}

MainWindow::~MainWindow()
{


    delete ui;
}

2.4 移动对象到线程:调用工作对象的moveToThread()方法,将其移动到新创建的线程中。

2.5 启动线程并调用槽函数:

调用线程对象的start()方法启动线程。

使用信号和槽机制,在主线程中发射信号,触发工作对象在新线程中执行的槽函数。
线程间通信:同样可以通过信号和槽机制在主线程和工作线程之间进行通信。工作线程可以发射信号,主线程连接这些信号到相应的槽函数,以处理线程返回的数据或状态。

2.6 UI

三、效果

在计算过程中,去拖动GUI会发现有点卡,这是因为现在GUI和排序都在主线程中执行。多线程就是解决这种类似的问题。

补充:

在qt里打开cmake文件就可以识别到qt工程了。

参考链接(如有侵权,请联系删除):

Qt开发编程-入门基础教程Qt5_哔哩哔哩_bilibili

四、问题补充

4.1 继承 QThread 重写 run ()方法和QObject::moveToThread ()方法的使用情况讨论

对 QT 两种多线程方案的优劣判断、以及 QObject::moveToThread() 配合线程池的优化思路,精准命中了 QT 多线程开发的核心痛点和最佳实践。下面我们分点拆解,既肯定你的正确判断,也补充细节让逻辑更完整:

4.1.1 先明确:你的 3 个核心判断全部成立

1. 「继承 QThread 重写 run ()」不适合 "子任务繁杂且数量多" 的场景 → 正确

继承 QThread 重写 run() 的本质是「一个线程绑定一个任务」:每个自定义 QThread 子类对应一个独立的系统线程,run() 函数就是该线程的核心执行逻辑(类似 "单线程任务")。

这种方案的局限性完全如你所说:

  • 线程资源浪费:子任务繁杂时,若每个任务都 new 一个 QThread,会创建大量系统线程(远超 CPU 核心数);
  • 切换开销激增:CPU 核心数是有限的(比如 8 核),超过核心数的线程会频繁上下文切换,反而导致整体效率下降;
  • 灵活性极差:run() 若写死为单一任务逻辑,无法动态添加多个子任务;若想在 run() 中处理多个任务,需手动实现任务队列和调度,复杂度堪比重复造轮子。

补充一个关键细节:QThreadrun() 默认会启动线程的「事件循环」(exec()),但如果重写 run() 时没有调用 exec(),线程会在 run() 执行完后直接退出,此时该线程中的 QObject 信号槽(依赖事件循环)会失效 ------ 这进一步限制了其处理多任务的能力。

2. 「QObject::moveToThread ()」更灵活,可 "一个 QThread 管理多个任务" → 正确

QObject::moveToThread() 的核心优势是「线程与任务解耦」:QThread 仅作为 "线程容器"(管理系统线程的生命周期),任务逻辑封装在 QObject 子类的槽函数中,通过信号触发执行。

你说的 "一个 QThread 管理多个任务",本质是:

  • 一个 QThread 对应一个系统线程,线程内运行「QT 事件循环」;
  • 多个 QObject 任务(或同一个 QObject 的多个槽函数)可被移动到同一个 QThread 中;
  • 通过发射不同信号,触发该线程内的不同槽函数(任务),这些任务会在同一个线程内串行执行(依赖事件循环调度)。

这种设计的灵活性体现在:

  • 无需继承 QThread,任务逻辑(QObject 子类)可独立封装、复用;
  • 线程可 "复用":启动一次线程后,通过信号多次触发不同任务,避免频繁创建销毁线程;
  • 任务调度简单:QT 事件循环自动处理槽函数的执行顺序,无需手动管理。

补充:若多个任务需要并行执行 ,可创建多个 QThread,每个线程管理一批任务;若任务量极大,直接用线程池(QThreadPool)更高效(下文展开)。

3. 「moveToThread () + QThreadPool」进一步减小开销 → 正确

QThreadPool 是 QT 原生的线程池实现,核心作用是「管理线程资源、自动调度任务」,与 moveToThread() 配合(或直接结合 QRunnable),完美解决 "大量子任务" 的高效执行问题:

  • 减少线程开销:线程池会预创建一定数量的线程(默认与 CPU 核心数匹配),任务执行完后线程不销毁,而是放回池中复用,避免频繁创建 / 销毁线程的系统开销;
  • 自动负载均衡:线程池根据任务数量和线程空闲状态,自动分配任务到空闲线程,充分利用 CPU 多核资源;
  • 简化开发:无需手动管理 QThread 的生命周期(创建、启动、退出),只需将任务封装为 QRunnable 子类,提交到线程池即可。

这里补充一个常见实践:QThreadPool 常与 QtConcurrent::run() 配合(底层是线程池),无需手动封装 QRunnable,直接将任务函数提交到线程池,代码更简洁;若任务需要发射信号(如进度更新),可结合 moveToThread()QObject 任务提交到线程池管理的线程中,兼顾灵活性和高效性。

4.1.2 补充 2 个关键细节(避免理解偏差)

1. "一个 QThread 管理多个任务" 是「串行执行」,而非「并行」

同一个 QThread 中的多个任务(槽函数),会在该线程的事件循环中串行执行(同一时间只能执行一个任务)。

  • 适合场景:任务之间无需并行、且单个任务耗时不长(如数据预处理、简单计算),避免多线程切换开销;
  • 不适合场景:多个任务都是 CPU 密集型、需要同时执行(此时需用多个 QThread 或线程池,实现并行)。
2. 继承 QThread 并非 "完全无用",需按需选型

虽然你说的 "多任务场景" 下 moveToThread() 更优,但继承 QThread 仍有适用场景:

  • 单一、长期运行的后台任务(如网络监听、串口数据接收):run() 中写死循环监听,无需事件循环,资源占用更低;
  • 对线程控制精度要求极高的场景(如手动控制线程暂停、恢复、优先级):QThread 提供 setPriority()wait() 等接口,更易精细控制。

4.1.3 总结(印证你的思路)

你的判断精准抓住了 QT 多线程开发的核心:

  • 「继承 QThread 重写 run ()」→ 适合单一长期任务,多任务场景效率低、灵活性差;
  • 「QObject::moveToThread ()」→ 适合任务灵活、需解耦的场景,可一个线程串行处理多个任务,避免线程浪费;
  • 「moveToThread () + QThreadPool」→ 适合子任务繁杂且数量多的场景,线程池自动调度,充分利用多核,减小系统开销。

4.2 QObject::moveToThread ()方法中的线程资源释放

注意:

复制代码
gen = new Generate(); // 任务对象必须无父对象(moveToThread 要求)

方法一:给 QThread 指定父对象(这里的this就是MainWindow),只能保证 QThread 对象的内存被释放,但无法自动释放系统线程资源。必须同时做以下两步,才能完整释放线程资源:

  1. 给 QThread 指定父对象(确保对象内存被父对象析构时释放);
  2. 在父对象析构前,调用 t1->quit()(让线程事件循环退出) + t1->wait()(等待系统线程完全终止),确保系统线程资源被释放。

方法二:没有指定父对象的情况下,使用对象的自带方法去释放资源

示例工程链接:

Molesidy/QT_Projects

相关推荐
yenggd42 分钟前
samba服务配置原理
服务器·开发语言·php
你不是我我1 小时前
【Java 开发日记】阻塞队列有哪些?拒绝策略有哪些?
java·开发语言
2201_757830871 小时前
反射的概念
java·开发语言
Vanranrr1 小时前
Python vs PowerShell:自动化 C++ 配置文件的两种实现方案
c++·python·自动化
先知后行。1 小时前
QT项目学习(自用)
c++·qt
周杰伦_Jay1 小时前
【Conda 完全指南】环境管理+包管理从入门到精通(含实操示例+表格对比)
开发语言·人工智能·微服务·架构·conda
努力学习的小廉1 小时前
【QT(一)】—— QT Creator的安装与使用
开发语言·qt
阿巳helloWorld1 小时前
SpringMVC底层流程解析
java·开发语言
开始了码1 小时前
qt::JSON文件介绍和操作
qt·json