1175 字
6 分钟
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 = 把工人搬去那边
- 信号/槽 = 发邮件通知主公司,数据干活好了
┌──────────────────┐ ┌──────────────────────┐│ MainWindow (GUI) │◄────────────┤ Worker::resultReady│ 主线程 │ │ 子线程信号 │└──────────────────┘ └──────────────────────┘ │ ▲ │ │ ▼ │ ┌────────────┐ moveToThread() ┌────────────┐ │ QThread │◄───────────────────────│ Worker │ │ 子线程控制器 │ │ 干活的对象 │ └────────────┘ └────────────┘Qt多线程中的重要概念是”事件循环“。每个线程可以拥有一个事件循环,通过moveToThread() 把 Worker 放进去,它就“生活在”那个线程中了。
线程中的代码不能够直接访问GUI,会引起线程安全问题。只能通过信号将结果传回主线程。
项目示例
mainwindow.cpp
#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;}整个线程的创建步骤为:
- 线程对象与Worker对象创建
worker = new Worker; // 创建Worker对象worker->moveToThread(&workerThread); // 将Worker对象绑定到线程- Worker对象:负责实际耗时操作的业务逻辑(需继承QObject)。
- moveToThread():将Worker的事件循环绑定到workerThread线程。此后Worker的槽函数会在新线程中执行。
- 线程生命周期管理
connect(&workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));connect(&workerThread, SIGNAL(finished()), &workerThread, SLOT(deleteLater()));线程结束时自动清理资源:
- 当线程finished()时,触发worker->deleteLater(),确保Worker对象在事件循环中安全删除。
- 线程自身也通过deleteLater()自动销毁,避免内存泄漏。
- 信号-槽通信机制 主线程通知Worker开始工作:
connect(this, SIGNAL(startWork(QString)), worker, SLOT(doWork1(QString)));- 跨线程通信:通过发射startWork信号,触发Worker的doWork1槽函数。
- 队列连接(Queued Connection):由于Worker位于不同线程,Qt自动使用队列连接,确保槽函数在目标线程执行。
Worker反馈结果到主线程:
connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handlerResults(QString)));- 线程安全更新UI:Worker通过信号resultReady传递结果,主线程的handlerResults接收并处理(如更新界面),符合“UI操作必须在主线程”原则。
- 线程启动与停止 启动线程:
void MainWindow::pushButton1Clicked() { if (!workerThread.isRunning()) { workerThread.start(); // 启动线程的事件循环 } emit startWork("正在运行"); // 触发Worker开始工作}- start():启动线程的事件循环,等待处理任务。
- 发射信号:通知Worker在新线程中执行doWork1。
停止线程:
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 → workerThread.start()启动线程。
- 主线程发射startWork信号 → Worker的doWork1在新线程执行。
- Worker处理完成 → 发射resultReady信号 → 主线程更新UI。
- 用户点击按钮2或窗口关闭 → 调用stopWork()终止任务,线程退出并清理资源。
Qt 通过继承QObject实现多线程
https://arkmostima.pages.dev/posts/qt多线程/