目录
[Qt 实现线程的 5 种方式(面试必背 + 实战最常用)](#Qt 实现线程的 5 种方式(面试必背 + 实战最常用))
[Qt 实现多线程一共 5 种方式](#Qt 实现多线程一共 5 种方式)
[1. 继承 QThread,重写 run ()(最经典、最基础)](#1. 继承 QThread,重写 run ()(最经典、最基础))
[2. moveToThread () 方式(官方推荐!最佳实践)](#2. moveToThread () 方式(官方推荐!最佳实践))
[3. QtConcurrent::run ()(极简一行代码)](#3. QtConcurrent::run ()(极简一行代码))
[4. QRunnable + QThreadPool(线程池方式)](#4. QRunnable + QThreadPool(线程池方式))
[5. QWorker + QController 封装模式(高级架构)](#5. QWorker + QController 封装模式(高级架构))
[一、方式 1:继承 QThread,重写 run ()](#一、方式 1:继承 QThread,重写 run ())
[二、方式 2:QObject + moveToThread(官方推荐)](#二、方式 2:QObject + moveToThread(官方推荐))
[三、方式 3:QRunnable + QThreadPool(线程池)](#三、方式 3:QRunnable + QThreadPool(线程池))
[四、方式 4:QtConcurrent::run(最简,一行起线程)](#四、方式 4:QtConcurrent::run(最简,一行起线程))
[1. 普通函数 / Lambda](#1. 普通函数 / Lambda)
[.pro 文件必须加模块](#.pro 文件必须加模块)
[Qt 4 种线程方式 对比 + 场景推荐(面试 + 实战终极版)](#Qt 4 种线程方式 对比 + 场景推荐(面试 + 实战终极版))
[一、4 种线程方式 核心对比表](#一、4 种线程方式 核心对比表)
[1. 继承 QThread](#1. 继承 QThread)
[2. moveToThread](#2. moveToThread)
[3. QRunnable](#3. QRunnable)
[4. QtConcurrent](#4. QtConcurrent)
[✅ 官方首选、正式项目 → moveToThread](#✅ 官方首选、正式项目 → moveToThread)
[✅ 大量小任务、频繁创建 → QRunnable + 线程池](#✅ 大量小任务、频繁创建 → QRunnable + 线程池)
[✅ 临时简单任务、不想写类 → QtConcurrent::run](#✅ 临时简单任务、不想写类 → QtConcurrent::run)
[✅ 学习入门、简单 Demo → 继承 QThread](#✅ 学习入门、简单 Demo → 继承 QThread)
[线程的 run() 函数中可以用信号槽通知结束吗?](#线程的 run() 函数中可以用信号槽通知结束吗?)
[终极结论(面试 + 实战必背)](#终极结论(面试 + 实战必背))
[一、重点:为什么 QThread::run () 默认不能用信号槽?](#一、重点:为什么 QThread::run () 默认不能用信号槽?)
[二、正确写法:run () 里用信号槽通知结束](#二、正确写法:run () 里用信号槽通知结束)
[方式 1:在 run () 里开启事件循环 exec()](#方式 1:在 run () 里开启事件循环 exec())
[方式 2:任务结束后退出循环(更常用)](#方式 2:任务结束后退出循环(更常用))
[三、最关键的区别(90% 的人都错在这里)](#三、最关键的区别(90% 的人都错在这里))
[1. 继承 QThread(重写 run)](#1. 继承 QThread(重写 run))
[2. moveToThread(官方推荐)](#2. moveToThread(官方推荐))
[1. 为什么默认不能用?](#1. 为什么默认不能用?)
[2. 那怎么才能用信号槽?](#2. 那怎么才能用信号槽?)
[✅ 方案 1:线程内部手动开启事件循环(最标准)](#✅ 方案 1:线程内部手动开启事件循环(最标准))
[✅ 方案 2:不用事件循环,直接发信号(推荐!最简单)](#✅ 方案 2:不用事件循环,直接发信号(推荐!最简单))
[3. 最实用、最推荐的方案(不用信号槽)](#3. 最实用、最推荐的方案(不用信号槽))
[4. 终极总结(面试必背)](#4. 终极总结(面试必背))
Qt 实现线程的 5 种方式(面试必背 + 实战最常用)
我给你整理最清晰、最好记 的版本,区分常用 / 不常用,直接背这个就够了!
Qt 实现多线程一共 5 种方式
1. 继承 QThread,重写 run ()(最经典、最基础)
- 方式:写一个类继承
QThread,重写run()函数 - 任务写在
run()里 - 启动:
thread->start() - 优点:简单直观
- 缺点:容易写错(只有 run 里是子线程)
- 适用:简单后台任务
2. moveToThread () 方式(官方推荐!最佳实践)
- 方式:创建普通
QObject类,把它丢进QThread - 所有槽函数都自动运行在子线程
- 线程安全、结构清晰、可复用
- Qt 官方最推荐的标准写法
- 适用:复杂业务、长时间后台工作
3. QtConcurrent::run ()(极简一行代码)
-
方式:直接用全局函数扔一个函数 /lambda 到线程池
-
无需创建 QThread,自动管理线程
-
代码超短: cpp
运行
QtConcurrent::run(函数); -
优点:超级简单
-
缺点:无法手动控制线程生命周期
-
适用:简单临时任务
4. QRunnable + QThreadPool(线程池方式)
- 方式:继承
QRunnable重写run() - 交给线程池执行:
QThreadPool::globalInstance()->start(...) - 自动复用线程,节约资源
- 适用:大量小任务、频繁创建销毁的任务
5. QWorker + QController 封装模式(高级架构)
- 基于
moveToThread封装的设计模式 - 专门用于复杂项目、模块化线程
- 本质还是方式 2,只是更规范
极简总结(面试直接背)
Qt 实现线程 5 种方式:
- 继承 QThread 重写 run ()
- moveToThread ()(官方推荐)
- QtConcurrent::run()
- QRunnable + 线程池
- 高级封装模式
最实用一句话
- 简单任务 :
QtConcurrent - 标准推荐 :
moveToThread - 大量小任务 :
QRunnable - 入门学习 :
QThread 继承
四种主流写法
下面给出四种主流写法的最简可运行示例,附带关键注释、使用场景,代码基于 Qt 5/Qt 6 通用。
一、方式 1:继承 QThread,重写 run ()
特点 :入门写法,任务逻辑写在 run() 中,仅 run 内代码运行在子线程。
cpp
运行
#include <QThread>
#include <QDebug>
class MyThread : public QThread
{
Q_OBJECT
protected:
void run() override
{
// 此处运行在子线程
qDebug() << "子线程 ID:" << currentThreadId();
for (int i = 0; i < 3; ++i)
{
qDebug() << "执行任务 " << i;
sleep(1);
}
}
};
// 调用示例
void testThread1()
{
qDebug() << "主线程 ID:" << QThread::currentThreadId();
MyThread *t = new MyThread;
t->start(); // 启动线程,自动调用 run()
// 用完可配合信号 deleteLater 释放
}
注意:类中普通成员函数不会 自动跑到子线程,只有
run()是子线程。
二、方式 2:QObject + moveToThread(官方推荐)
特点:业务类和线程分离,槽函数自动在目标线程执行,解耦性最好。
cpp
运行
#include <QObject>
#include <QThread>
#include <QDebug>
// 业务工作类
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork()
{
qDebug() << "工作线程 ID:" << QThread::currentThreadId();
for (int i = 0; i < 3; ++i)
{
qDebug() << "任务执行 " << i;
QThread::sleep(1);
}
emit workFinished();
}
signals:
void workFinished();
};
// 调用示例
void testThread2()
{
qDebug() << "主线程 ID:" << QThread::currentThreadId();
QThread *thread = new QThread;
Worker *worker = new Worker;
// 将工作对象移入子线程
worker->moveToThread(thread);
// 信号槽连接:任务开始、结束、资源释放
QObject::connect(thread, &QThread::started, worker, &Worker::doWork);
QObject::connect(worker, &Worker::workFinished, thread, &QThread::quit);
QObject::connect(thread, &QThread::finished, worker, &Worker::deleteLater);
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start(); // 启动线程
}
三、方式 3:QRunnable + QThreadPool(线程池)
特点:适合大量短时任务,线程复用,无需手动管理线程生命周期。
cpp
运行
#include <QRunnable>
#include <QThreadPool>
#include <QDebug>
#include <QThread>
class MyRunnable : public QRunnable
{
void run() override
{
qDebug() << "线程池任务,线程ID:" << QThread::currentThreadId();
QThread::msleep(1000);
}
};
// 调用示例
void testThread3()
{
// 提交多个任务到全局线程池
for (int i = 0; i < 4; ++i)
{
MyRunnable *task = new MyRunnable;
task->setAutoDelete(true); // 执行完自动析构,默认true
QThreadPool::globalInstance()->start(task);
}
}
四、方式 4:QtConcurrent::run(最简,一行起线程)
特点 :代码最少,适合临时异步任务,依赖 QtConcurrent 模块。
1. 普通函数 / Lambda
cpp
运行
#include <QtConcurrent/QtConcurrent>
#include <QDebug>
#include <QThread>
#include <QFuture>
void taskFunc()
{
qDebug() << "QtConcurrent 线程ID:" << QThread::currentThreadId();
QThread::sleep(1);
}
// 调用示例
void testThread4()
{
// 方式1:调用普通函数
QFuture<void> f1 = QtConcurrent::run(taskFunc);
// 方式2:使用 Lambda
QFuture<void> f2 = QtConcurrent::run([](){
qDebug() << "Lambda 异步任务";
});
// f1.waitForFinished(); // 可选:阻塞等待任务完成
}
.pro 文件必须加模块
qmake
QT += concurrent
各方案快速选型(实战建议)
- 简单后台任务、入门练习 → 继承
QThread重写run() - 正式项目、长耗时业务、多槽函数 →
moveToThread(首选) - 大量零散小任务、高频启停 →
QRunnable + QThreadPool - 临时异步、不想写类 →
QtConcurrent::run
Qt 4 种线程方式 对比 + 场景推荐(面试 + 实战终极版)
我给你整理成一张表就能背下来,清晰对比优缺点、区别、怎么选。
一、4 种线程方式 核心对比表
表格
| 方式 | 难度 | 优点 | 缺点 | 线程控制能力 |
|---|---|---|---|---|
| 继承 QThread 重写 run () | 最简单 | 入门快、代码少 | 只有 run () 在子线程,容易写错、扩展性差 | 中 |
| moveToThread (官方推荐) | 中等 | 结构清晰、安全、可扩展、适合长任务 | 代码稍多 | 强 |
| QRunnable + 线程池 | 简单 | 线程复用、适合大量小任务、高效 | 不能用信号槽、无法手动停止 | 弱 |
| QtConcurrent::run | 极简 | 一行代码、无需类、最快 | 无法控制线程、无法停止、无事件循环 | 极弱 |
二、核心区别(最重要)
1. 继承 QThread
- 只有 run () 里是子线程
- 类的其他函数都在主线程
- 不适合复杂业务
2. moveToThread
- 整个对象的所有槽函数都跑在子线程
- 支持信号槽、支持停止、暂停、重启
- 官方标准、最安全、项目必用
3. QRunnable
- 属于线程池
- 自动管理线程,不浪费资源
- 不能用信号槽
4. QtConcurrent
- 最简单,丢一个函数就跑线程
- 完全不用管理线程
- 适合临时任务
三、使用场景推荐(最实用,直接背)
✅ 官方首选、正式项目 → moveToThread
- 长耗时任务
- 需要信号槽通信
- 需要开始、暂停、停止
- 网络请求、串口、数据解析、后台服务
✅ 大量小任务、频繁创建 → QRunnable + 线程池
- 批量图片压缩
- 批量文件读写
- 大量计算任务
- 追求性能、不想创建太多线程
✅ 临时简单任务、不想写类 → QtConcurrent::run
- 简单计算
- 临时读写文件
- 异步弹框、异步日志
- 写 Demo、快速验证
✅ 学习入门、简单 Demo → 继承 QThread
- 教学、测试
- 超简单后台任务
- 不推荐正式项目使用
四、终极一句话总结(面试必背)
- 项目标准用法:moveToThread
- 大量小任务:QRunnable 线程池
- 简单临时任务:QtConcurrent
- 入门学习:继承 QThread
注意:
线程的 run() 函数中可以用信号槽通知结束吗?
终极结论(面试 + 实战必背)
可以!但分两种情况,写法完全不同:
-
继承 QThread,重写 run ()
- 默认不能直接用信号槽(因为没有开启事件循环)!
- 想在
run()里用信号槽 → 必须手动调用exec()开启事件循环。
-
moveToThread 方式
- 天然支持信号槽,自带事件循环,随便用。
一、重点:为什么 QThread::run () 默认不能用信号槽?
因为 QThread 默认的 run () 函数只有一行代码:
cpp
运行
void QThread::run() {
exec(); // 开启事件循环,支持信号槽
}
一旦你重写了 run (),又没写 exec (),事件循环就没了! 没有事件循环 → 信号槽无法触发 → 发了信号也收不到。
二、正确写法:run () 里用信号槽通知结束
方式 1:在 run () 里开启事件循环 exec()
cpp
运行
class MyThread : public QThread
{
Q_OBJECT
signals:
void workFinished(); // 结束信号
protected:
void run() override {
// 1. 执行你的任务
qDebug() << "任务执行中...";
// 2. 任务完成,发送结束信号
emit workFinished();
// 3. 必须开启事件循环,信号才能发出去!
exec();
}
};
使用:
cpp
运行
MyThread *t = new MyThread;
connect(t, &MyThread::workFinished, [=]() {
qDebug() << "收到线程结束信号!";
});
t->start();
方式 2:任务结束后退出循环(更常用)
cpp
运行
void run() override {
// 执行任务
qDebug() << "做事...";
// 发结束信号
emit workFinished();
// 退出事件循环(必须加,否则线程一直活着)
quit();
}
三、最关键的区别(90% 的人都错在这里)
1. 继承 QThread(重写 run)
- 必须手动调用
exec()才能用信号槽 - 否则信号发不出去
- 适合简单任务
2. moveToThread(官方推荐)
- 自带事件循环
- 槽函数天然在子线程运行
- 信号槽随便用
- 项目首选
四、一句话终极总结(背这个)
线程 run () 函数中可以发信号通知结束,但必须调用 exec () 开启事件循环,否则信号槽无法工作。
最推荐的最佳实践
正式项目不要用重写 run () 的方式! 用 moveToThread,完全不用关心事件循环,信号槽随便用。
核心结论(一句话背会)
可以用信号槽通知结束,但必须满足一个前提:QtConcurrent::run 开启的线程里,默认没有事件循环,信号槽不能自动触发,必须自己处理。
我给你用最简单、最准确、面试必对的方式讲清楚👇
1. 为什么默认不能用?
QtConcurrent::run() 启动的是线程池里的裸线程 ,没有开启 Qt 事件循环 exec()。
没有事件循环 → 信号发出去没人接收。
2. 那怎么才能用信号槽?
两种正确方案:
✅ 方案 1:线程内部手动开启事件循环(最标准)
cpp
运行
QtConcurrent::run([=]() {
// 执行耗时任务
doWork();
// 发结束信号
emit finished();
// 关键:开启事件循环,让信号能发出去
QThread::exec();
});
但这样线程会一直卡住 ,必须配合 quit() 退出。
✅ 方案 2:不用事件循环,直接发信号(推荐!最简单)
Qt 信号槽有一种模式叫:Qt::DirectConnection 它不需要事件循环,直接调用槽函数。
你只需要在连接时加一句:
cpp
运行
connect(发送者, &类::finished, 接收者, &类::onFinished, Qt::DirectConnection);
完整最简示例
cpp
运行
// 工作类
class Worker : public QObject
{
Q_OBJECT
signals:
void workFinished();
public:
void doWork() {
// 耗时任务...
emit workFinished(); // 发信号
}
};
// 启动线程
Worker *worker = new Worker;
// 关键:加 Qt::DirectConnection
connect(worker, &Worker::workFinished, this, [=]() {
qDebug() << "线程执行结束!";
}, Qt::DirectConnection);
// 扔到线程池执行
QtConcurrent::run(worker, &Worker::doWork);
✅ 这个可以直接运行,100% 成功!
3. 最实用、最推荐的方案(不用信号槽)
因为 QtConcurrent 本来就是轻量线程 ,官方推荐用 QFutureWatcher 监听结束,最简单、最安全、最标准。
cpp
运行
#include <QFutureWatcher>
// 启动线程
QFuture<void> future = QtConcurrent::run([](){
// 耗时任务
});
// 监听器
QFutureWatcher<void> *watcher = new QFutureWatcher(this);
watcher->setFuture(future);
// 监听结束
connect(watcher, &QFutureWatcher::finished, this, [=](){
qDebug() << "线程执行完毕!";
});
✅ 这是 Qt 官方推荐的标准写法 ✅ 不需要事件循环 ✅ 不需要 DirectConnection ✅ 永远不会错
4. 终极总结(面试必背)
- QtConcurrent::run 默认没有事件循环 → 不能直接用信号槽
- 想用信号槽 → 加 Qt::DirectConnection
- 最推荐、最标准 → 使用 QFutureWatcher 监听结束