Qt5 C++环境下多模块独立线程运行与通信
在 Qt5.15 + C++ 环境下,将子模块(A、B、C)放入独立线程中运行,并通过主模块 M 的界面按钮控制其启停,同时支持模块间通信,推荐使用 QObject + moveToThread 模式(而非继承 QThread),Qt 官方推荐的线程使用方式。
下面是一个完整的简化示例:
主窗口(M)提供“启动/停止”按钮;
子模块 A、B、C 各自封装为 QObject 子类;
每个子模块运行在独立的 QThread 中;
使用信号与槽实现线程安全的跨模块通信;
所有资源在退出时正确释放。
1. 子模块基类(可选,用于统一接口)
// SubModuleBase.h
#pragma once
#include <QObject>
class SubModuleBase : public QObject
{
Q_OBJECT
public:
explicit SubModuleBase(QObject *parent = nullptr) : QObject(parent) {}
public slots:
virtual void startWork() = 0;
virtual void stopWork() = 0;
signals:
void finished(); // 工作完成信号(可用于通知主线程)
void logMessage(const QString &msg); // 日志输出
};2. 子模块 A(B、C 类似)
// ModuleA.h
#pragma once
#include "SubModuleBase.h"
#include <QTimer>
class ModuleA : public SubModuleBase
{
Q_OBJECT
public:
explicit ModuleA(QObject *parent = nullptr);
public slots:
void startWork() override;
void stopWork() override;
private slots:
void doWork(); // 实际工作函数
private:
QTimer *m_timer; // 模拟周期性任务
volatile bool m_running = false;
};
// ------------------------------------------------------------------------------
// ModuleA.cpp
#include "ModuleA.h"
#include <QDebug>
ModuleA::ModuleA(QObject *parent)
: SubModuleBase(parent)
{
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &ModuleA::doWork);
}
void ModuleA::startWork()
{
if (m_running) return;
m_running = true;
m_timer->start(1000); // 每秒执行一次
emit logMessage("ModuleA started");
}
void ModuleA::stopWork()
{
if (!m_running) return;
m_running = false;
m_timer->stop();
emit logMessage("ModuleA stopped");
emit finished(); // 可选:通知主线程已停止
}
void ModuleA::doWork()
{
if (!m_running) return;
static int count = 0;
QString msg = QString("ModuleA working... count=%1").arg(++count);
qDebug() << msg;
emit logMessage(msg);
// 示例:向其他模块发送消息(通过主模块中转或直接连接)
// 此处假设主模块会连接此信号到其他模块的槽
}3. 主模块(M)
// MainWindow.h
#pragma once
#include <QMainWindow>
#include <QThread>
#include "ModuleA.h"
#include "ModuleB.h"
#include "ModuleC.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_btnStartA_clicked();
void on_btnStopA_clicked();
// B、C 类似...
void onLogReceived(const QString &msg);
private:
Ui::MainWindow *ui;
// 子模块对象
ModuleA *m_moduleA;
ModuleB *m_moduleB;
ModuleC *m_moduleC;
// 线程
QThread *m_threadA;
QThread *m_threadB;
QThread *m_threadC;
};
// ------------------------------------------------------------------------------
// MainWindow.cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QMessageBox>
#include <QMetaObject>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建子模块对象(尚未移动到线程)
m_moduleA = new ModuleA();
m_moduleB = new ModuleB();
m_moduleC = new ModuleC();
// 创建线程
m_threadA = new QThread(this);
m_threadB = new QThread(this);
m_threadC = new QThread(this);
// 将子模块移动到对应线程
m_moduleA->moveToThread(m_threadA);
m_moduleB->moveToThread(m_threadB);
m_moduleC->moveToThread(m_threadC);
// 启动线程(线程开始运行事件循环)
m_threadA->start();
m_threadB->start();
m_threadC->start();
// 连接 UI 按钮信号到槽
connect(ui->btnStartA, &QPushButton::clicked, this, &MainWindow::on_btnStartA_clicked);
connect(ui->btnStopA, &QPushButton::clicked, this, &MainWindow::on_btnStopA_clicked);
// B、C 按钮类似...
// 连接日志信号到主界面显示(线程安全!)
connect(m_moduleA, &ModuleA::logMessage, this, &MainWindow::onLogReceived, Qt::QueuedConnection);
connect(m_moduleB, &ModuleB::logMessage, this, &MainWindow::onLogReceived, Qt::QueuedConnection);
connect(m_moduleC, &ModuleC::logMessage, this, &MainWindow::onLogReceived, Qt::QueuedConnection);
// 示例:模块间通信(A → B)
connect(m_moduleA, &ModuleA::logMessage, m_moduleB, &ModuleB::receiveFromA, Qt::QueuedConnection);
// 注意:m_moduleB 的 receiveFromA 必须是槽函数,且定义在 ModuleB 中
}
MainWindow::~MainWindow()
{
// 先请求停止工作
QMetaObject::invokeMethod(m_moduleA, "stopWork", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_moduleB, "stopWork", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_moduleC, "stopWork", Qt::QueuedConnection);
// 等待线程结束(可加超时)
m_threadA->quit();
m_threadB->quit();
m_threadC->quit();
m_threadA->wait(2000);
m_threadB->wait(2000);
m_threadC->wait(2000);
delete ui;
// QObject parent-child 机制会自动 delete m_moduleX(因为它们没有 parent,需手动 delete 或设 parent)
delete m_moduleA;
delete m_moduleB;
delete m_moduleC;
}
void MainWindow::on_btnStartA_clicked()
{
// 通过 invokeMethod 调用线程中的槽(线程安全)
QMetaObject::invokeMethod(m_moduleA, "startWork", Qt::QueuedConnection);
}
void MainWindow::on_btnStopA_clicked()
{
QMetaObject::invokeMethod(m_moduleA, "stopWork", Qt::QueuedConnection);
}
void MainWindow::onLogReceived(const QString &msg)
{
// 在主线程更新 UI(如 QTextEdit)
ui->textLog->append(msg);
}4. 模块间通信补充说明
直接连接:如 connect(A, SIGNAL(x), B, SLOT(y)) 是线程安全的,Qt 会自动使用 QueuedConnection;
避免直接调用对方方法:应通过信号/槽或 QMetaObject::invokeMethod; (??如果直接调用对方方法意味着什么??)
共享数据:若需共享状态,建议使用 QMutex 保护,或通过信号传递副本(推荐后者,更安全);
5. 注意事项
所有子模块的槽函数在线程中执行;
不要在子模块构造函数中启动定时器或做耗时操作(此时还未 moveToThread);
析构顺序很重要:先 quit + wait 线程,再 delete 对象;
使用 Qt::QueuedConnection 确保跨线程调用安全(Qt 默认对跨线程连接使用 Queued);
避免在子线程中操作 GUI(如 QLabel、QTextEdit 等),所有 UI 更新必须在主线程。
目录 返回
首页