Qt5 C++环境下多模块独立线程运行与通信

15 02月
作者:cinjep|分类:应用笔记

在 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 更新必须在主线程。


浏览26
返回
目录
返回
首页
Qt5.15.2 C++ 使用 UDP 发送 Wake-on-LAN(WOL)魔术包唤醒远端计算机