一、简介
在 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 架构优势
- 界面响应流畅:GUI 线程不被耗时运算阻塞,用户点击、拖拽等操作实时反馈;
- 运算效率提升:耗时任务在独立线程执行,充分利用 CPU 多核资源,避免单线程瓶颈;
- 代码结构清晰:GUI 交互逻辑与运算业务逻辑分离,便于维护和扩展;
- 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()中处理多个任务,需手动实现任务队列和调度,复杂度堪比重复造轮子。
补充一个关键细节: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 对象的内存被释放,但无法自动释放系统线程资源。必须同时做以下两步,才能完整释放线程资源:
- 给 QThread 指定父对象(确保对象内存被父对象析构时释放);
- 在父对象析构前,调用
t1->quit()(让线程事件循环退出) +t1->wait()(等待系统线程完全终止),确保系统线程资源被释放。


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

示例工程链接: