Qt 通过继承QObject实现多线程

Qt实现多线程

最简单的多线程示例包含:

  • MainWindow:主线程,负责GUI
  • Worker:干活的对象
  • Worker::resultReady:子线程发出的信号
  • QThread:线程控制器
名称 类型 职责 说明
MainWindow QWidget 子类 图形界面,主线程运行 控制整个应用,接收子线程数据
QThread Qt线程类 提供线程上下文(事件循环) 不是干活的,是“搬家工人 + 开个房间”
Worker QObject 子类 真正干活的对象 会被搬进 QThread 管理的线程空间
QObject::moveToThread(QThread*) 方法 把对象“搬”进线程空间 只有 QObject 的子类可以这么搬
QTimer 定时器 定期调用 Worker::timeout() 运行在线程内,不阻塞主线程
  • 主线程 = 总公司,负责 UI 显示
  • QThread = 新开了一个工厂的“独立办公室”
  • Worker = 工人在新办公室干活
  • moveToThread = 把工人搬去那边
  • 信号/槽 = 发邮件通知主公司,数据干活好了
1
2
3
4
5
6
7
8
9
10
11
┌──────────────────┐              ┌──────────────────────┐
│ MainWindow (GUI) │◄────────────┤ Worker::resultReady
│ 主线程 │ │ 子线程信号 │
└──────────────────┘ └──────────────────────┘
│ ▲
│ │
▼ │
┌────────────┐ moveToThread() ┌────────────┐
│ QThread │◄───────────────────────│ Worker │
│ 子线程控制器 │ │ 干活的对象 │
└────────────┘ └────────────┘

Qt多线程中的重要概念是”事件循环“。每个线程可以拥有一个事件循环,通过moveToThread()Worker 放进去,它就“生活在”那个线程中了。

线程中的代码不能够直接访问GUI,会引起线程安全问题。只能通过信号将结果传回主线程。

项目示例

mainwindow.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include "mainwindow.h"

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实例 */
worker = new Worker;

/* 将worker通过movetoThread()绑定至线程管理器 */
worker->moveToThread(&workerThread);

/* 线程完成后销毁worker和workerThread */
connect(&workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(&workerThread, SIGNAL(finished()), &workerThread, SLOT(deleteLater()));

/* 主线程向worker发送开始信号,worker开始在新线程工作 */
connect(this, SIGNAL(startWork(QString)), worker, SLOT(doWork1(QString)));

/* 主线程接收到worker发送来的信号,通知结果已准备好 */
connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handlerResults(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() << "线程结束" << Qt::endl;
}
}

/* 按钮1回调函数 */
void MainWindow::pushButton1Clicked()
{
const QString str = "正在运行";

if (!workerThread.isRunning()){
/* 开启线程 */
workerThread.start();
}

/* 发送信号至线程,通知worker开始工作 */
emit this->startWork(str);
}

/* 按钮2回调函数 */
void MainWindow::pushButton2Clicked()
{
if (workerThread.isRunning()){
/* worker停止工作 */
worker->stopWork();
}
}

void MainWindow::handlerResults(const QString & results)
{
qDebug() << "线程的状态" << results << Qt::endl;
}

整个线程的创建步骤为:

  1. 线程对象与Worker对象创建
1
2
worker = new Worker;                       // 创建Worker对象
worker->moveToThread(&workerThread); // 将Worker对象绑定到线程
  • Worker对象:负责实际耗时操作的业务逻辑(需继承QObject)。
  • moveToThread():将Worker的事件循环绑定到workerThread线程。此后Worker的槽函数会在新线程中执行。
  1. 线程生命周期管理
1
2
connect(&workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(&workerThread, SIGNAL(finished()), &workerThread, SLOT(deleteLater()));

线程结束时自动清理资源:

  • 当线程finished()时,触发worker->deleteLater(),确保Worker对象在事件循环中安全删除。
  • 线程自身也通过deleteLater()自动销毁,避免内存泄漏。
  1. 信号-槽通信机制
    主线程通知Worker开始工作:
1
connect(this, SIGNAL(startWork(QString)), worker, SLOT(doWork1(QString)));
  • 跨线程通信:通过发射startWork信号,触发Worker的doWork1槽函数。
  • 队列连接(Queued Connection):由于Worker位于不同线程,Qt自动使用队列连接,确保槽函数在目标线程执行。

Worker反馈结果到主线程:

1
connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handlerResults(QString)));
  • 线程安全更新UI:Worker通过信号resultReady传递结果,主线程的handlerResults接收并处理(如更新界面),符合“UI操作必须在主线程”原则。
  1. 线程启动与停止
    启动线程:
1
2
3
4
5
6
void MainWindow::pushButton1Clicked() {
if (!workerThread.isRunning()) {
workerThread.start(); // 启动线程的事件循环
}
emit startWork("正在运行"); // 触发Worker开始工作
}
  • start():启动线程的事件循环,等待处理任务。
  • 发射信号:通知Worker在新线程中执行doWork1。

停止线程:

1
2
3
4
5
6
7
8
9
10
11
void MainWindow::pushButton2Clicked() {
if (workerThread.isRunning()) {
worker->stopWork(); // 请求Worker停止工作
}
}

MainWindow::~MainWindow() {
worker->stopWork(); // 停止Worker
workerThread.quit(); // 结束事件循环
workerThread.wait(2000); // 等待线程退出
}
  • stopWork():Worker内部应检查标志(如QAtomicInt)安全退出循环。
  • quit() + wait():优雅终止线程,quit()退出事件循环,wait()阻塞等待线程结束。
  1. 线程执行流程
  • 用户点击按钮1 → workerThread.start()启动线程。
  • 主线程发射startWork信号 → Worker的doWork1在新线程执行。
  • Worker处理完成 → 发射resultReady信号 → 主线程更新UI。
  • 用户点击按钮2或窗口关闭 → 调用stopWork()终止任务,线程退出并清理资源。

Qt 通过继承QObject实现多线程
http://akichen891.github.io/2025/04/23/Qt多线程/
作者
Aki
发布于
2025年4月23日
更新于
2025年4月23日
许可协议