详细介绍:Qt 多线程
QThread类提供了一种独立于平台的方法来管理线程 。 QThread对象在程序中管理一个控制线程。 QThreads在 run()中开始执行。默认情况下, run()通过调用 exec()来启动事件循环,并在线程中运行 Qt事件循环。您可以通过使用 QObject::moveToThread()将 worker对象移动到线程来使用它们。
下面详细介绍 Qt 中的 QThread,包括其原理、常用用法、两种工作模式、信号槽机制、常见坑点与最佳实践。
✅ 1. QThread 是什么?
QThread 是 Qt 中的线程类,用来在应用程序中创建和控制线程,用于执行耗时任务,从而避免主线程(UI 线程)阻塞。
Qt 的线程具有如下特点:
每个 QThread 代表一个操作系统线程
QObject 的事件循环与线程绑定(非 QObject 的行为则不受 Qt 控制)
线程之间可以安全使用信号-槽通信(自动跨线程调用)
✅ 2. QThread 的两种使用方式
Qt 官方推荐方式 2:对象移入线程
方式 1:继承 QThread 并重写 run() 方法
你把任务写进 run(),线程启动后自动执行。
示例
class MyThread : public QThread {
Q_OBJECT
public:
void run() override {
// 耗时任务
for (int i = 0; i < 5; ++i) {
QThread::sleep(1);
qDebug() << "Running in thread:" << QThread::currentThread();
}
}
};
使用:
MyThread *t = new MyThread();
t->start();
特点
✔ 简单直观
✔ run() 是线程主函数
❌ 不能使用 QTimer、信号槽等事件循环(除非开启 exec())
❌ 不适合复杂对象结构
❌ Qt 官方不推荐
方式 2(推荐):使用工作对象 + moveToThread()
保持 QThread 只表示“线程”,逻辑由普通 QObject 执行。
示例:正确的做法
class Worker : public QObject {
Q_OBJECT
public slots:
void doWork() {
for (int i = 0; i < 5; ++i) {
QThread::sleep(1);
emit progress(i);
}
}
signals:
void progress(int value);
};
QThread *thread = new QThread();
Worker *worker = new Worker();
worker->moveToThread(thread);
QObject::connect(thread, &QThread::started, worker, &Worker::doWork);
QObject::connect(worker, &Worker::progress, [](int v){
qDebug() << "progress:" << v;
});
QObject::connect(worker, &Worker::progress, thread, &QThread::quit); // 完成后退出线程
thread->start();
特点
✔ 使用 Qt 事件循环
✔ 支持定时器、信号槽
✔ 推荐方式,最安全、最灵活
✔ 易于控制生命周期
✅ 3. QThread 的信号与槽 —— 自动跨线程
Qt 的信号槽机制自动识别跨线程情况:
若 sender 和 receiver 在不同线程
→ 自动使用 QueuedConnection(队列连接)
→ 函数在 receiver 所在线程执行若在同一个线程
→ 使用直接调用(Direct)
因此 QThread 非常适合线程间通信。
✅ 4. QThread 中的事件循环
线程事件循环由 exec() 启动:
在方式 2 中,默认 QThread 会自动调用 exec()(因为内部 run() 调用 exec())。
如果你选择方式 1 自己重写 run():
void run() override {
exec(); // 必须显式调用,才能使用事件循环(Timers、Signals)
}
没有事件循环则:
QTimer 不工作
跨线程信号槽不会执行
✅ 5. QThread 的常见错误
❌ 错误:在子线程直接操作 UI
label->setText("abc"); // 错误!UI 只能在主线程处理
❌ 错误:delete QThread 后工作对象还在线程中
必须确保线程退出,然后再 delete。
✅ 6. 正确关闭 QThread
thread->quit(); // 发出事件循环退出信号
thread->wait(); // 阻塞等待线程结束
不要直接调用terminate()(会强制终止线程,导致资源泄露)。
✅ 7. 使用 QThreadPool / QRunnable 代替 QThread
对于大量短任务,推荐:
QThreadPoolQtConcurrent::run()
它们自动管理线程池,更高效。
⭐ 8. 总结
| 特性 | 继承 QThread | moveToThread(推荐) |
|---|---|---|
| 支持事件循环 | ❌ 默认不支持 | ✔ 支持 |
| 支持信号槽 | 较差 | ✔ 完整支持 |
| 易维护性 | 较差 | ✔ 最佳 |
| 官方推荐 | ❌ 不推荐 | ✔ 推荐 |
/**********************************************************************************************************/
讲解Qt 提供的线程同步类:QMutex、QMutexLocker、QReadLocker 和 QWriteLocker,并分析它们的使用场景和区别。
1️⃣ QMutex(互斥锁)
作用
QMutex 用于保护共享资源,确保同一时刻只有一个线程访问该资源。它是 最基础的互斥锁。
常用方法
QMutex mutex;
// 加锁
mutex.lock();
// 解锁
mutex.unlock();
// 尝试加锁,不阻塞
if (mutex.tryLock()) {
// 成功加锁
mutex.unlock();
}
特点
如果一个线程已加锁,其他线程会阻塞,直到锁被释放
可以防止数据竞争(race condition)
示例
QMutex mutex;
int counter = 0;
void increment() {
mutex.lock();
counter++;
mutex.unlock();
}
2️⃣ QMutexLocker(互斥锁的智能封装)
作用
QMutexLocker 是 RAII 风格(资源获取即初始化)的锁管理类,它能自动加锁和解锁,防止忘记 unlock 或异常导致死锁。
用法
QMutex mutex;
void increment() {
QMutexLocker locker(&mutex); // 自动 lock
counter++;
} // 离开作用域自动 unlock
特点
自动管理 lock/unlock
避免手动调用 lock/unlock 时出错
强烈推荐在函数内部局部变量使用
3️⃣ QReadWriteLock(读写锁)
在 Qt 中,读写锁由 QReadWriteLock 实现,同时支持多个读线程,但写线程是互斥的。
工作模式
共享读锁(read lock):多个线程可以同时读取
排他写锁(write lock):写线程独占,写时阻塞其他读/写线程
QReadLocker / QWriteLocker
它们是 QReadWriteLock 的 RAII 封装类,自动管理锁的加解锁。
QReadLocker
QReadWriteLock lock;
void readData() {
QReadLocker locker(&lock); // 加读锁
// 读取共享数据
} // 自动解锁
QWriteLocker
void writeData() {
QWriteLocker locker(&lock); // 加写锁
// 修改共享数据
} // 自动解锁
特点
读操作不会阻塞其他读线程
写操作阻塞所有读写线程
RAII 风格,安全可靠
4️⃣ QMutex / QReadWriteLock 的区别
| 特性 | QMutex | QReadWriteLock |
|---|---|---|
| 允许同时读线程 | ❌ 不允许 | ✔ 允许 |
| 写线程互斥 | ✔ | ✔ |
| 性能 | 简单,锁/解锁开销低 | 适合读多写少场景,读效率高 |
| 使用场景 | 小规模互斥,临界区短 | 共享资源读多写少,读操作频繁 |
5️⃣ 使用建议
简单互斥 → 用
QMutex或QMutexLocker读多写少 → 用
QReadWriteLock+QReadLocker/QWriteLockerRAII 风格优先 → 使用 Locker,避免忘记 unlock
不要在 UI 线程长时间加锁 → 防止界面卡死
6️⃣ 综合示例
QReadWriteLock lock;
int sharedData = 0;
void reader() {
QReadLocker locker(&lock); // 加读锁
qDebug() << "Read data:" << sharedData;
}
void writer() {
QWriteLocker locker(&lock); // 加写锁
sharedData++;
}
多个 reader 可以同时执行
writer 执行时,reader 和其他 writer 都会阻塞
总结:
QMutex + QMutexLocker → 简单互斥,保护临界区
QReadWriteLock + QReadLocker / QWriteLocker → 适合读多写少的共享资源
Locker 类优先 → 自动解锁,安全可靠
一、继承QThread 的线程 例程
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
#include
class WorkerThread; //声明类
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr); //构造函数,设置父控件
~MainWindow();
private:
WorkerThread *workerThread; //创建对象
QPushButton *pushButton; //使用此按钮点击后开启线程
private slots:
void handleResults(const QString &result); //用于接收线程发送的信号
void pushButtonClicked(); //点击按钮开启线程
};
//创建一个WorkerThread 类继承于 QThread
class WorkerThread : public QThread {
Q_OBJECT //用到信号槽即需要此宏定义
public:
WorkerThread(QWidget *parent = nullptr) { //构造函数,设置夫控件
Q_UNUSED(parent); //避免未使用参数的警告
}
//重写 run 方法,继承 QThread 的类,只有 run 方法是在新的线程里面
void run() override {
QString result = "线程开启成功";
//这里写比较耗时的操作
//...
//延时2s,把延时当耗时操作
sleep(2);
emit resultReady(result); //emit关键字标记信号的发射,发射信号
}
signals:
void resultReady(const QString &s); //定义信号,用于通知结果已准备好,在槽函数里使用
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setGeometry(0,0,800,480);
pushButton = new QPushButton(this);
workerThread = new WorkerThread(this);
pushButton->resize(100,40);
pushButton->setText("开启线程");
connect(workerThread,SIGNAL(resultReady(QString)),
this,SLOT(handleResults(QString)));
connect(pushButton,SIGNAL(clicked()),
this,SLOT(pushButtonClicked()));
}
MainWindow::~MainWindow()
{
//进程退出,注意这里run()方法没有写循环,该方法需要有循环才生效
workerThread->quit();
//阻塞等待2秒检查一次进程是否退出
if(workerThread->wait(2000)) {
qDebug()<<"线程已结束!"<isRunning()){
workerThread->start();
}
}

这是一个非常好的、与 Qt 多线程编程 密切相关的问题。理解 QObject 在线程中的行为是使用 Qt 进行并发编程的关键。
Qt 继承 QObject 的线程 形象讲解
在 Qt 中,**线程亲和性(Thread Affinity)**是一个核心概念,它决定了一个 QObject 对象“属于”哪个线程。
形象比喻:线程与家
你可以把一个 线程 (QThread) 想象成一个 “家”,而一个 QObject 对象 就像一个 “家庭成员”。
出生地原则(Creation Thread):
QObject在哪个线程中被创建,它就天然地“出生”并属于那个线程。这是它的初始亲和性。
独居与派对(Event Loop):
每个线程(家)如果想要处理 信号与槽的异步连接(
Qt::QueuedConnection)或 定时器事件、网络事件 等,它就必须有一个 事件循环(Event Loop),就像家里的一个“派对/待办事项处理中心”。QThread 的
run()方法 默认会启动一个事件循环。
搬家(
moveToThread()):QObject对象可以通过调用obj->moveToThread(targetThread)来“搬家”,改变它所属于的线程。限制: 如果这个“家庭成员”已经有了一个“家长”(即设置了
parent),那么它就不能再搬家了。有父对象(Parent)的 QObject 必须和它的父对象待在同一个线程中。
⚠️ 为什么不应该直接继承 QThread 来执行任务?
传统的(但不推荐的)Qt 线程用法是继承 QThread 并重写 run()。但是,QThread 对象本身是线程的控制器/管理者,它通常属于创建它的线程(比如主线程),而不是它所运行的新线程。
错误示范: 如果你直接在继承
QThread的子类中添加QObject成员和槽函数,你可能会在新线程中执行任务,但这些槽函数(因为它继承自QObject)却可能在 主线程 中被调用,造成线程安全问题!
✅ 推荐做法:Worker 模式
最安全和推荐的做法是使用 Worker 对象模式:
创建 Worker: 创建一个继承自
QObject的类 (Worker),它包含你想要在新线程中执行的任务代码。创建 Thread: 创建一个
QThread实例。搬家: 调用
worker->moveToThread(thread),将Worker对象“搬”到新线程中。启动: 调用
thread->start()启动线程(开启 Worker 的新家)。信号/槽通信: 在主线程(UI 线程)和 Worker 线程之间使用 信号(Signals) 和 槽(Slots) 进行通信,这是 Qt 线程安全的黄金法则。因为
QObject在搬家后,其槽函数(通过队列连接)就会在新线程的事件循环中被安全执行。
C++ 示例:Worker 对象模式
我们创建一个 Worker 类来执行耗时的任务,并将其移动到一个新的 QThread 中。
1. Worker 类 (Worker.h/Worker.cpp)
这个类继承自 QObject,包含实际要执行的任务。
Worker.h
#ifndef WORKER_H
#define WORKER_H
#include
#include
#include
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
public slots:
// **注意:这个槽函数将在 Worker 对象所属的线程中执行**
void doWork(const QString ¶meter);
signals:
// 任务完成时发出的信号
void workFinished(const QString &result);
};
#endif // WORKER_H
Worker.cpp
#include "Worker.h"
Worker::Worker(QObject *parent) : QObject(parent)
{
// Worker 对象的初始线程亲和性是创建它的线程(通常是主线程)
qDebug() << "Worker created in thread:" << QThread::currentThreadId();
}
void Worker::doWork(const QString ¶meter)
{
// 任务执行时,检查当前线程 ID。
// 如果成功调用了 moveToThread,这个 ID 应该与主线程 ID 不同。
qDebug() << "Starting work in thread:" << QThread::currentThreadId()
<< "with parameter:" << parameter;
// 模拟耗时的计算
long long sum = 0;
for (int i = 0; i < 100000000; ++i) {
sum += i;
}
QString result = QString("Calculation finished. Sum: %1").arg(sum);
qDebug() << "Work finished in thread:" << QThread::currentThreadId();
// 任务完成,通过信号通知主线程
emit workFinished(result);
}
2. 主函数 (main.cpp)
我们将 Worker 实例移动到新创建的 QThread 实例中。
main.cpp
#include
#include
#include "Worker.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "Main thread ID:" << QThread::currentThreadId();
// 1. 创建 QThread(新的“家”)
QThread* thread = new QThread();
// 2. 创建 Worker 对象(“家庭成员”),它最初在主线程中
Worker* worker = new Worker();
// 3. **核心步骤:搬家!**
// 将 worker 对象移动到新的 QThread 实例中。
// 此时,worker 的槽函数(如 doWork)将在 thread 线程中执行。
worker->moveToThread(thread);
// 4. 建立连接(线程间通信)
// A. 任务启动:主线程 --> 新线程
// 线程启动时,立即调用 worker 的 doWork 槽函数
QObject::connect(thread, &QThread::started, worker, [worker](){
// 注意:doWork 是一个槽函数,会在 thread 启动后,在新线程中被调用。
worker->doWork("Heavy Task Data");
});
// B. 结果接收:新线程 --> 主线程
// Worker 完成工作后,发信号通知主线程
QObject::connect(worker, &Worker::workFinished, [](const QString& result){
qDebug() << "Received result in thread:" << QThread::currentThreadId();
qDebug() << "Result:" << result;
// 收到结果后可以停止应用,或者进行UI更新等操作
QCoreApplication::quit();
});
// C. 清理工作(可选,但推荐):确保线程和对象被正确删除
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
QObject::connect(thread, &QThread::finished, worker, &QObject::deleteLater);
// 5. 启动线程(开启新的“家”的事件循环)
thread->start();
// 运行事件循环,等待结果信号
return a.exec();
}
总结关键点:
Worker继承QObject: 使其具备信号/槽机制和线程亲和性。moveToThread(): 改变Worker对象的线程亲和性,让它的槽函数在新线程的事件循环中安全执行。QThread::start(): 启动新线程,并在新线程中开始事件循环,等待事件(如信号触发槽函数)。信号与槽: 在不同线程之间安全通信的桥梁。Qt 会自动使用
Qt::QueuedConnection来保证跨线程调用的安全。
这个模式保证了耗时操作在一个独立的线程中运行,不会阻塞主线程(UI 线程),同时所有 QObject 的事件处理都发生在它所属的线程中,从而避免了数据竞争和崩溃。
/*********************************************************************************************************/
堆对象和栈对象:
new 出来的对象 → 堆 → 可以 deleteLater
普通写法声明的对象 → 栈 → 不能 deleteLater
| 写法 | 类型 | 内存位置 | 生命周期 | 可否 deleteLater |
|---|---|---|---|---|
QThread workerThread; | 栈对象 | stack | 自动释放 | ❌ 禁止 |
QThread *workerThread = new QThread; | 堆对象 | heap | 手动释放 | ✔ 可以 deleteLater |
QThread *workerThread;(未 new) | 仅是指针 | 与对象无关 | 指针不管理对象 | N/A |
✅ 判断是否为堆对象的核心规则
✔ 使用 new 创建的对象 → 堆对象
QThread *t = new QThread; // 堆对象
必须手动 delete 或者用 deleteLater。
✔ 作为类成员指针,但未 new,本身不是对象
class A { QThread *t; // 只是指针,本身不是对象 };
必须配套:
t = new QThread(); // 才会创建堆对象
/*************************************************************************************************************/
槽函数介绍:
下面给你一个 最完整、最清晰、最实用的 Qt connect() 介绍,包括四种写法、语法、规则、优缺点,以及 Qt5 / Qt6 差异。
QObject::connect(
const QObject *sender,
PointerToMemberFunction signal,
const QObject *receiver,
PointerToMemberFunction slot
);
| 参数序号 | 参数名称 | 类型 | 含义 | 作用 |
| 1 | sender | const QObject* | 发送者对象 | 哪个对象发出了信号。通常是触发事件的控件(如按钮、滑动条等)或数据对象。 |
| 2 | signal | 信号函数指针 | 信号 | 哪个信号被发出。这是发送者对象的一个成员函数,在特定事件发生时被调用。 |
| 3 | receiver | const QObject* | 接收者对象 | 哪个对象要响应这个信号。通常是包含槽函数(处理逻辑)的窗口或业务对象。 |
| 4 | slot | 槽函数指针 | 槽函数 | 执行什么操作来响应信号。这是接收者对象的一个成员函数,当信号发出时它会被自动执行。 |
结构如下:
什么是 connect
四种写法(宏版、函数指针版、lambda版、自动连接)
参数规则
线程行为
常见错误
最推荐用法
① connect 是干什么的?
Qt 的核心机制:信号(signal)和槽(slot)
当一个对象产生事件(比如按钮点击)→ 发射 signal
另一个对象就可以响应(执行 slot)
connect() 就是绑定两者的函数。
② connect() 的四种写法
✅ 1. Qt4 老式 SIGNAL / SLOT 宏写法(常见但不推荐)
connect(sender, SIGNAL(signalName(Type1, Type2)),
receiver, SLOT(slotName(Type1, Type2)));
示例:
connect(button, SIGNAL(clicked()),
this, SLOT(onButtonClicked()));
特点:
字符串匹配,不检查类型
写错里面的名字不会报错
运行期才发现错误 → 难调试
Qt5/6 中继续支持
✅ 2. Qt5/Qt6 新式 函数指针写法(最推荐)
connect(sender, &SenderClass::signalName,
receiver, &ReceiverClass::slotName);
示例:
connect(button, &QPushButton::clicked,
this, &MainWindow::onButtonClicked);
优点:
编译期类型检查(写错直接编译报错)
没有字符串开销
自动推断参数类型
支持重载信号(通过
QOverload)
✔ Qt 官方推荐
✔ 最安全
✔ 最快
⚠ 3. 使用 lambda 表达式的写法(非常常用)
connect(sender, &SenderClass::signalName, this, [=](){
// 写你想干的事
});
例子:
connect(button, &QPushButton::clicked, this, [=](){
qDebug() << "Button clicked!";
});
优点:
非常灵活
不需要写单独的槽函数
可以捕获变量
缺点:
lambda 不会自动断开信号
容易造成悬挂回调(dangling)
⚠ 4. 自动连接(基于命名规则)
如果你写槽函数名为:
void on_pushButton_clicked();
Qt 会自动连接,不需要写 connect。
**优点:**简单
**缺点:**重构后容易失效,不推荐大量使用
③ connect 的参数格式(信号槽断言)
信号槽必须参数一致:
| 属性 | 要求 |
|---|---|
| 参数数量 | 必须一致 |
| 参数类型 | 必须一致 |
| 参数顺序 | 必须一致 |
| 参数名 | 无要求 |
示例:
signals:
void valueChanged(int value);
slots:
void onValueChanged(int value); // ✔
void onValueChanged(QString s); // ❌ 类型不同
④ connect 与多线程行为(非常重要)
如果信号来自某线程,槽在另一线程中
Qt 会自动进行线程切换:
✔ Qt::AutoConnection(默认)
如果 sender 和 receiver 在同一个线程 → 直接调用
如果不在同一个线程 → 事件队列调用(异步)
等价于:
Qt::QueuedConnection
这就是 Qt 的事件驱动线程模型。
⑤ connect 常见错误
| 错误写法 | 说明 |
|---|---|
| SIGNAL / SLOT 名字写错 | 不会报错,但不会连接 |
| SIGNAL 参数类型不一致 | connect 返回 false |
| doSomething 写成 doSometing | 编译不报错(老式写法) |
| connect 到不存在的槽函数 | 槽不执行 |
| 跨线程调用 GUI | 程序崩溃 |
⑥ connect 的返回值
connect 返回 QMetaObject::Connection
你可以用它断开连接:
QMetaObject::Connection c;
c = connect(...);
disconnect(c);
⑦ 推荐使用方式(最安全最现代)
推荐:函数指针 + lambda
connect(button, &QPushButton::clicked, this, [this](){
handleButton();
});
跨线程(Worker 中的信号)
connect(worker, &Worker::resultReady,
this, &MainWindow::handleResults);
⑧ 总结(你必须记住)
✔ connect 的最常用写法
connect(sender, &SenderClass::signalName,
receiver, &ReceiverClass::slotName);
✔ SIGNAL/SLOT 宏仍被 Qt 支持,但不推荐
✔ lambda 版很灵活,广泛使用
✔ 跨线程自动使用队列连接,实现线程安全回调
✔ 栈对象不能 deleteLater
✔ 指针对象(new)可以 deleteLater
本小节案例:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
#include
#include
#include
class Worker; //声明工人类
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr); //构造函数,设置父控件
~MainWindow();
private:
//创建对象
QPushButton *pushButton1; //开启线程按钮
QPushButton *pushButton2; //打断线程按钮
QThread workerThread; //全局线程,这里创建的是栈对象
Worker *worker; //工人类
private slots:
void pushButton1Clicked(); //点击开始线程
void pushButton2Clicked(); //点击打断线程
void handleResults(const QString &); //用于接收工人是否在工作的信号
signals:
void startWork(const QString &); //工人开始工作(做些耗时的操作)
};
//Worker类,这个嘞声明了doWorker1函数,将整个Worker类移至线程workerThread
class Worker : public QObject {
Q_OBJECT
private:
QMutex lock; //互斥锁
bool isCanRun; //标志位
public:
// Worker():isCanRun(false){} //初始化
public slots:
//耗时的工作都放在槽函数之下,工人可以有多份不同的工作,但每次只能去做一份
void doWork1(const QString ¶meter) {
isCanRun = true; //标志位为真
while(1) {
//此处 {} 的作用是 QMutexLocker 与 lock 的作用范围。获取锁后,
//运行完成即解锁
{
QMutexLocker locker(&lock);
if(!isCanRun){ //如果标志位不为真
break;
}
}
QThread::sleep(2);
emit resultReady(parameter + "doWorker1函数");
}
//doWork1运行完成,发送信号
emit resultReady("打断doWorke1函数");
}
//void doWorker2()...
public:
//打断线程(注意此方法不能放在槽函数下)
void stopWork(){
qDebug()<<"打断线程"<
#include "mainwindow.h"
#include
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setGeometry(0,0,800,480);
pushButton1 = new QPushButton(this);
pushButton2 = new QPushButton(this);
pushButton1->setGeometry(300,200,80,40);
pushButton2->setGeometry(400,200,80,40);
pushButton1->setText("开启线程");
pushButton2->setText("打断线程");
worker = new Worker; //工人类实例化。继承 QObject的多线程类不能指定父对象
worker->moveToThread(&workerThread); //将worker类移至线程workerThread
//线程完成销毁对象
connect(&workerThread, SIGNAL(finished()), //workerThread为栈对象
worker, SLOT(deleteLater()));
//这里不能使用deletelater,因为创建的是栈对象,只有堆对象才是手动删除
// connect(&workerThread, SIGNAL(finished()),
// &workerThread,SLOT(deleteLater()));
//发送开始工作的信号,开始工作
connect(this, SIGNAL(startWork(QString)),
worker, SLOT(doWork1(QString)));
//接受到worker发送过来的信号
connect(worker, SIGNAL(resultReady(QString)),
this, SLOT(handleResults(QString)));
//点击按钮开始线程
connect(pushButton1, SIGNAL(clicked()),
this, SLOT(pushButton1Clicked()));
//点击按钮关闭线程
connect(pushButton2, SIGNAL(clicked()),
this, SLOT(pushButton2Clicked()));
}
MainWindow::~MainWindow()
{
//打断线程再退出
worker->stopWork();
workerThread.quit();
//阻塞后,判断线程是否结束
if(workerThread.wait(2000)){
qDebug()<<"线程结束"<startWork(str);
}
void MainWindow::pushButton2Clicked(){
if(workerThread.isRunning()){
//停止耗时工作,跳出耗时工作的循环
worker->stopWork();
}
}
void MainWindow::handleResults(const QString &results){
qDebug()<<"线程状态:"<
函数执行流程、函数间关系、析构逻辑与用函数指针写 connect 的详解
下面我把你这段线程/worker 示例里的执行流程拆成若干步骤逐一讲清楚,解释每个函数/连接的作用、相互联系,为什么析构函数要那样写,最后给出现代(函数指针/lambda)写法的完整示例和替换方法。
一、总体运行脉络(高层流程概览)
MainWindow 构造时
worker = new Worker;(在堆上创建 Worker)
worker->moveToThread(&workerThread);:把 worker 的 QObject 所属线程切换为 workerThread(后续其槽在 workerThread 上运行)。
建立信号/槽连接:
MainWindow::startWork(QString) → Worker::doWork1(QString)(当主线程发 startWork 时,slot 在 workerThread 执行,跨线程会变为队列调用)。
Worker::resultReady(QString) → MainWindow::handleResults(QString)(worker 发结果回主线程,handleResults 在主线程执行)。
QThread::finished() → worker->deleteLater()(线程结束时销毁 worker)。
按钮连接到 pushButton1Clicked() / pushButton2Clicked()。
点击“开启线程”按钮
如果 workerThread 未运行:workerThread.start() 启动线程(并在该线程中运行事件循环)。
emit startWork("正在运行"):发射信号,Qt 根据线程归属决定调用方式。因为 MainWindow(sender)在主线程、worker(receiver)在 workerThread,默认是 Qt::AutoConnection → 会变成 Qt::QueuedConnection(即把调用封装成事件投递到 workerThread 的事件队列)。
Worker::doWork1 在 workerThread 执行
doWork1 被事件队列调度到 workerThread 上执行(槽函数在该线程上下文中运行)。
在 doWork1 中循环每 2 秒 emit resultReady(...),这会把信号发回主线程(因为 MainWindow::handleResults 在主线程)——同样是队列调用,handleResults 在主线程运行,打印日志。
点击“打断线程”按钮
主线程调用 worker->stopWork()(该函数取得互斥锁,设置 isCanRun=false),doWork1 在下一轮循环检查锁后看到 isCanRun==false 就跳出循环并退出槽函数。
doWork1 结束后,若需要退出线程的事件循环,应由主线程调用 workerThread.quit()(退出事件循环)或由 worker 自己决定(通常由主线程调用 quit())。然后 workerThread.wait() 阻塞等待线程结束。
析构(MainWindow::~MainWindow)
典型做法:先通知 worker 停止(worker->stopWork()),再 workerThread.quit(),再 workerThread.wait(…) 等待线程结束。这样可以保证线程内的槽运行结束、资源安全释放,避免在 MainWindow 或应用退出时出现悬挂/竞态/崩溃。
二、关键函数/动作之间的具体联系(谁调用谁、在哪个线程)
worker->moveToThread(&workerThread)
→ worker 的 QObject::thread() 被设置为 workerThread。这意味着:
以后对 worker 的槽(slots)进行队列调用时,这些槽会在 workerThread 的线程上下文执行。
worker 的事件处理(例如 deleteLater() 或 QTimer)都在 workerThread 的事件循环中进行。
connect(this, SIGNAL(startWork(QString)), worker, SLOT(doWork1(QString)));
→ 当 this(MainWindow)在主线程发 startWork 时,因为 worker 不在主线程,Qt 使用 QueuedConnection(默认)把对 doWork1 的调用封装成事件,放入 workerThread 的事件队列。
emit resultReady(...)(在 workerThread 中)
→ MainWindow::handleResults() 会在主线程执行(QueuedConnection),所以在该槽里可以安全更新 UI(打印日志、更新界面等)。
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
→ 线程结束(finished)时安排删除 worker(deleteLater 会在 worker 所在线程的事件循环中执行删除)。这是为了避免直接 delete 一个仍在其他线程中的 QObject。
三、析构函数里那些语句为啥要这样写(逐句解释)
你的析构常见写法:
MainWindow::~MainWindow()
{
worker->stopWork(); // 1
workerThread.quit(); // 2
if(workerThread.wait(2000)){ // 3
qDebug()<<"线程结束"<
解释:
worker->stopWork()
告诉 worker 停止循环(设置 isCanRun=false),让 doWork1 能够尽快退出循环并返回。这一步是优雅结束工作的重要信号;如果不做,doWork1 可能一直阻塞,线程永远不会结束。
workerThread.quit()
请求线程的事件循环退出(如果线程正在运行事件循环)。如果 doWork1 正在运行且阻塞,这个 quit() 只会在事件循环空闲时生效;因此必须先让 worker 自行退出循环。
workerThread.wait(2000)
阻塞主线程等待最多 2000 ms 直到 workerThread 结束。目的是在 MainWindow 被销毁(或应用退出)前确保 workerThread 完全结束,避免线程访问已释放资源导致崩溃。
如果 wait() 超时,你可能需要采取更强硬的手段(例如强制断言、记录日志),但通常应确保 worker 可以响应 stop 请求。
要点:顺序很重要 —— 先通知 worker 停止,再请求线程退出/等待。否则你可能会在 worker 正在运行时就释放 MainWindow 资源,出现悬挂指针或 crash。
四、并发安全与锁的作用(为什么用 QMutex + QMutexLocker)
isCanRun 被多个线程访问(主线程会调用 stopWork(),worker 线程在循环里读 isCanRun),这是共享数据,引发竞态条件。
用 QMutex + QMutexLocker 可以保证对 isCanRun 的读写互斥,从而避免数据竞争(尤其是 stopWork() 里写 isCanRun=false 与 doWork1 里读的竞态)。
另外的更简洁/高效的写法是使用 std::atomic<bool> 或 QAtomicInteger,这在只有 boolean 读写时更轻量,避免 mutex 开销。
五、stopWork() 为什么“不写在槽里”或“可以写为槽”?
实际上 stopWork() 可以写成 public slots:,这样它也能通过信号从其他线程以队列方式调用(更线程安全)。
但当前实现是主线程直接调用 worker->stopWork()(直接函数调用),这会在主线程中取得 QMutex,然后写 isCanRun=false。这是可行的,但注意:如果 stopWork() 在主线程调用并使用 QMutex,没有跨线程事件的序列问题。
若你希望主线程不直接访问 worker(更解耦),可以改成 emit requestStop() → connect(requestStop, worker, SLOT(stopWork())),这样会以队列方式在 workerThread 执行 stopWork()(但要注意:如果 doWork1 正在执行且没有处理事件,stopWork 也只是放入队列,不会马上执行)。因此对立即生效,直接调用 worker->stopWork() 能更快改变 isCanRun(前提是 stopWork() 不依赖于在 workerThread 中执行的上下文)。
六、doWork1 的阻塞问题与事件循环
doWork1 是个长时间运行的函数,它会占用 workerThread,阻塞该线程的事件循环(因为你在槽里做了一个 while(1) 循环),这意味着:
在 doWork1 运行期间,其他投递到 workerThread 的事件/槽(例如 stopWork() 如果通过 queued 信号投递)将等待,直到 doWork1 完成或主动调用 QCoreApplication::processEvents()。
因此常见建议:
如果你需要在线程中同时响应外部请求(比如 stop),要在循环中定期检查一个原子标志(如 isCanRun),并尽量避免长时间不检查,这样 stopWork() 执行后可以尽快退出。
或者把长期任务拆成小块,使用定时器或任务队列来处理,而不是一个无限循环。
七、现代写法:函数指针(推荐)与 lambda 替换示例
你当前用的是旧式 SIGNAL()/SLOT() 宏写法,建议用函数指针 / lambda 的现代写法,优点:编译时检查、类型安全。
把这些旧写法:
connect(&workerThread, SIGNAL(finished()),
worker, SLOT(deleteLater()));
connect(this, SIGNAL(startWork(QString)),
worker, SLOT(doWork1(QString)));
connect(worker, SIGNAL(resultReady(QString)),
this, SLOT(handleResults(QString)));
替换为推荐的现代写法:
// 推荐:使用函数指针 / 成员指针语法(编译期检查)
connect(&workerThread, &QThread::finished,
worker, &QObject::deleteLater);
connect(this, &MainWindow::startWork,
worker, &Worker::doWork1);
connect(worker, &Worker::resultReady,
this, &MainWindow::handleResults);
使用 lambda(有时很方便)
如果你想在发信号时直接做一点额外处理或绑定上下文:
connect(worker, &Worker::resultReady, this, [this](const QString &s){
qDebug() << "从worker收到:" << s;
// 可以在这里直接更新 UI
});
关于返回值 QMetaObject::Connection
你也可以保存连接句柄以便后续断开:
QMetaObject::Connection c = connect(worker, &Worker::resultReady,
this, &MainWindow::handleResults);
// later
disconnect(c);
八、完整现代化示例(把你的代码改用函数指针写法,做一些改进)
下面给出一个更稳健的、完整片段(只示核心部分):
// 在头文件里确保 signals / slots 名称匹配
signals:
void startWork(const QString &);
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
QThread workerThread; // 栈对象也可以
Worker *worker;
// MainWindow 构造
worker = new Worker;
worker->moveToThread(&workerThread);
// 当线程结束时删除 worker(worker 是堆对象,安全)
connect(&workerThread, &QThread::finished,
worker, &QObject::deleteLater);
// 现代写法:MainWindow -> Worker
connect(this, &MainWindow::startWork,
worker, &Worker::doWork1);
// Worker -> MainWindow(结果回调)
connect(worker, &Worker::resultReady,
this, &MainWindow::handleResults);
// 按钮
connect(pushButton1, &QPushButton::clicked,
this, &MainWindow::pushButton1Clicked);
connect(pushButton2, &QPushButton::clicked,
this, &MainWindow::pushButton2Clicked);
析构函数保持不变:
MainWindow::~MainWindow()
{
worker->stopWork(); // 通知 worker 停止
workerThread.quit(); // 请求线程退出事件循环
if (!workerThread.wait(2000)) {
qWarning() << "workerThread did not exit in time!";
// 视情况处理:日志/强制退出等
}
}
如果你想把 QThread 也放到堆上(动态创建),可以这样:
QThread *workerThread = new QThread(this); // parent 为 MainWindow,可在 MainWindow 销毁时自动删除
Worker *worker = new Worker;
worker->moveToThread(workerThread);
connect(workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
注:如果 workerThread 是 new 出来的堆对象,那么把 workerThread 的 finished 信号连到 workerThread->deleteLater() 是允许且合理的(线程对象会被安全删除)。但是当 workerThread 是栈对象(QThread workerThread;)时,绝对不要让线程自己 deleteLater() 自己——那会导致 delete 栈对象,崩溃。
九、补充 - 关于 deleteLater() 在跨线程的语义(注意点)
QObject::deleteLater() 将发送一个 DeferredDelete 事件到该对象所在线程的事件循环中执行删除(也就是对象会在所属线程安全地被删除)。
因此常见做法是:connect(thread, &QThread::finished, worker, &QObject::deleteLater),当线程结束时会安排删除 worker(因为 worker 的所属线程刚好是该线程)。注意:如果线程已经终止且事件循环不再处理事件,那么 deleteLater 可能无法立刻执行,但在 finished 发出时通常仍在可处理状态。另一种更强的办法是:在主线程等待 thread.wait() 完成后直接 delete worker(需要确保 worker 的线程已安全终止且不再运行)。
十、一些实践建议(小结)
推荐使用函数指针(&Class::signal / &Class::slot)或 lambda,避免使用宏 SIGNAL()/SLOT()。
Worker 如果在堆上 new,用 connect(thread, &QThread::finished, worker, &QObject::deleteLater) 清理它。
如果 QThread 是栈对象,不要把 deleteLater() 连接到它自己。
在析构前:先通知 worker 停止(atomic 或 mutex),再 quit()、wait()。
对共享标志用 QMutex 或 std::atomic<bool>,后者在只需要布尔读写时更高效。
如果槽是长耗时函数,确保它定期检查停止条件以便能迅速退出,否则 quit()/finished 可能起不到立即效果。
如果你愿意,我可以把你当前文件(.h + .cpp)全部改成现代安全风格并贴出完整可直接编译的代码(包含 Worker 构造初始化 isCanRun、使用 std::atomic<bool>、并把 connect 全部改成函数指针写法)。要我直接给完整代码吗?

浙公网安备 33010602011771号