1175 字
6 分钟
Qt 通过继承QObject实现多线程

Qt实现多线程#

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

  • MainWindow:主线程,负责GUI
  • Worker:干活的对象
  • Worker::resultReady:子线程发出的信号
  • QThread:线程控制器
名称类型职责说明
MainWindowQWidget 子类图形界面,主线程运行控制整个应用,接收子线程数据
QThreadQt线程类提供线程上下文(事件循环)不是干活的,是“搬家工人 + 开个房间”
WorkerQObject 子类真正干活的对象会被搬进 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;
}

整个线程的创建步骤为:

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

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

  • 当线程finished()时,触发worker->deleteLater(),确保Worker对象在事件循环中安全删除。
  • 线程自身也通过deleteLater()自动销毁,避免内存泄漏。
  1. 信号-槽通信机制 主线程通知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操作必须在主线程”原则。
  1. 线程启动与停止 启动线程:
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. 线程执行流程
  • 用户点击按钮1 → workerThread.start()启动线程。
  • 主线程发射startWork信号 → Worker的doWork1在新线程执行。
  • Worker处理完成 → 发射resultReady信号 → 主线程更新UI。
  • 用户点击按钮2或窗口关闭 → 调用stopWork()终止任务,线程退出并清理资源。
Qt 通过继承QObject实现多线程
https://arkmostima.pages.dev/posts/qt多线程/
作者
Aki
发布于
2025-04-23
许可协议
CC BY-NC-SA 4.0