常德市网站建设_网站建设公司_网站制作_seo优化
2026/1/17 15:01:12 网站建设 项目流程

一、简介

在 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

#ifndef MYTHREAD_H
#define MYTHREAD_H
#include 
#include 
class Generate : public QObject
{Q_OBJECT
public:explicit Generate(QObject *parent = nullptr);void working(int num);
signals:void sendArray(QVector num);
};
class BubbleSort : public QObject
{Q_OBJECT
public:explicit BubbleSort(QObject *parent = nullptr);void working(QVector list);
signals:void finish(QVector num);
};
class QuickSort : public QObject
{Q_OBJECT
public:explicit QuickSort(QObject *parent = nullptr);void working(QVector list);
private:void quickSort(QVector &list,int l,int r);
signals:void finish(QVector num);
};
#endif // MYTHREAD_H

mythread.c

#include "mythread.h"
#include 
#include 
#include 
#include 
Generate::Generate(QObject *parent): QObject{parent}
{}
void Generate::working(int num)
{qDebug()<< "生成随机数的线程地址" << QThread::currentThread();QElapsedTimer time;QVector 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 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 &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 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

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include 
#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

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "mythread.h"
#include 
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 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 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 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() 中处理多个任务,需手动实现任务队列和调度,复杂度堪比重复造轮子。

补充一个关键细节:QThread 的 run() 默认会启动线程的「事件循环」(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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询