Qt实现事件发布订阅系统

基于Qt的MetaCall事件实现的线程安全事件发布订阅系统,类似ROS的消息订阅模式。

特性

  • 线程安全:使用互斥锁保护订阅者列表,支持多线程环境
  • 多种发布模式:支持同步、异步当前线程、异步接收者三种发布模式
  • 灵活的回调:支持lambda表达式和函数对象作为回调
  • 自动管理:基于Qt的事件循环,自动处理线程间通信
  • 类型安全:使用QVariant传递数据,支持任意类型
  • 单例模式:全局唯一实例,方便使用

架构设计

代码

eventpubsub.h
cpp 复制代码
#ifndef EVENTPUBSUB_H
#define EVENTPUBSUB_H

#include <QObject>
#include <QString>
#include <QMap>
#include <QList>
#include <QMutex>
#include <QVariant>
#include <QThread>
#include <QEvent>
#include <functional>
#include <memory>

/**
 * @brief 发布类型枚举
 */
enum class PublishType {
    Sync,              // 同步:在当前线程立即执行
    AsyncCurrent,      // 异步当前线程:在当前线程的事件循环中执行
    AsyncReceiver      // 异步接收者:在接收者对象所在线程的事件循环中执行
};

/**
 * @brief 订阅者信息结构体
 */
struct SubscriberInfo {
    QObject *receiver;                    // 订阅者对象指针
    std::function<void(const QVariant &)> callback;  // 回调函数(lambda或函数对象)
    QThread *receiverThread;               // 接收者所在线程(用于异步接收者模式)
    
    SubscriberInfo() : receiver(nullptr), receiverThread(nullptr) {}
    
    SubscriberInfo(QObject *recv, std::function<void(const QVariant &)> cb)
        : receiver(recv), callback(cb), receiverThread(recv ? recv->thread() : nullptr) {}
    
    // 用于比较和查找
    bool operator==(const SubscriberInfo &other) const {
        return receiver == other.receiver && 
               receiverThread == other.receiverThread;
    }
};
/**
 * @brief MetaCall事件类,用于在事件循环中执行回调
 */
class CallbackEvent : public QEvent {
    public:
        static QEvent::Type EventType;
        
        CallbackEvent(std::function<void(const QVariant &)> cb, const QVariant &data)
            : QEvent(EventType), m_callback(cb), m_data(data) {}
        
        void execute() {
            if (m_callback) {
                m_callback(m_data);
            }
        }
        
    private:
        std::function<void(const QVariant &)> m_callback;
        QVariant m_data;
    };
    
    /**
     * @brief 接收者辅助对象,用于在接收者线程中处理回调事件
     */
    class ReceiverHelper : public QObject {
        Q_OBJECT
    public:
        explicit ReceiverHelper(QObject *parent = nullptr) : QObject(parent) {}
        
    protected:
        bool event(QEvent *e) override {
            if (e->type() == CallbackEvent::EventType) {
                CallbackEvent *callbackEvent = static_cast<CallbackEvent *>(e);
                callbackEvent->execute();
                return true;
            }
            return QObject::event(e);
        }
    };
    
/**
 * @brief 事件发布订阅系统
 * 
 * 基于Qt的MetaCall事件实现的线程安全事件发布订阅系统
 * 支持同步和异步两种发布模式
 */
class EventPubSub : public QObject {
    Q_OBJECT
    
public:
    /**
     * @brief 获取单例实例
     */
    static EventPubSub* instance();
    
    /**
     * @brief 订阅事件
     * @param topicId 主题ID(消息ID)
     * @param receiver 订阅者对象指针
     * @param callback 回调函数(lambda或函数对象)
     * @return 是否订阅成功
     */
    bool subscribe(const QString &topicId, 
                   QObject *receiver, 
                   std::function<void(const QVariant &)> callback);
    
    /**
     * @brief 取消订阅
     * @param topicId 主题ID
     * @param receiver 订阅者对象指针
     * @return 是否取消成功
     */
    bool unsubscribe(const QString &topicId, QObject *receiver);
    
    /**
     * @brief 取消某个对象的所有订阅
     * @param receiver 订阅者对象指针
     * @return 取消的订阅数量
     */
    int unsubscribeAll(QObject *receiver);
    
    /**
     * @brief 发布消息
     * @param topicId 主题ID
     * @param data 消息数据(QVariant类型,可以包含任意类型)
     * @param type 发布类型(默认异步接收者)
     */
    void publish(const QString &topicId, 
                 const QVariant &data, 
                 PublishType type = PublishType::AsyncReceiver);
    
    /**
     * @brief 获取某个主题的订阅者数量
     * @param topicId 主题ID
     * @return 订阅者数量
     */
    int getSubscriberCount(const QString &topicId) const;
    
    /**
     * @brief 获取所有主题ID列表
     * @return 主题ID列表
     */
    QStringList getAllTopics() const;
    
    /**
     * @brief 清空所有订阅
     */
    void clearAll();

protected:
    /**
     * @brief 自定义事件处理
     */
    bool event(QEvent *e) override;

private:
    explicit EventPubSub(QObject *parent = nullptr);
    ~EventPubSub();
    
    /**
     * @brief 同步执行回调
     */
    void executeCallbackSync(const SubscriberInfo &subscriber, const QVariant &data);
    
    /**
     * @brief 异步执行回调(当前线程)
     */
    void executeCallbackAsyncCurrent(const SubscriberInfo &subscriber, const QVariant &data);
    
    /**
     * @brief 异步执行回调(接收者线程)
     */
    void executeCallbackAsyncReceiver(const SubscriberInfo &subscriber, const QVariant &data);
    
    /**
     * @brief 获取或创建接收者的辅助对象(用于处理回调事件)
     */
    QObject* getOrCreateReceiverHelper(QObject *receiver);
    
    // 订阅者映射表:Map<主题ID, 订阅者列表>
    QMap<QString, QList<SubscriberInfo>> m_subscribers;
    
    // 接收者辅助对象映射:Map<接收者对象, 辅助对象>
    QMap<QObject*, ReceiverHelper*> m_receiverHelpers;
    
    // 线程安全锁
    mutable QMutex m_mutex;
    
    // 单例实例
    static EventPubSub* s_instance;
    static QMutex s_instanceMutex;
};


#endif // EVENTPUBSUB_H
eventpubsub.cpp
cpp 复制代码
#include "eventpubsub.h"
#include <QCoreApplication>
#include <QThread>
#include <QDebug>

// 静态成员初始化
EventPubSub* EventPubSub::s_instance = nullptr;
QMutex EventPubSub::s_instanceMutex;

// 自定义事件类型
QEvent::Type CallbackEvent::EventType = static_cast<QEvent::Type>(QEvent::User + 1);

EventPubSub::EventPubSub(QObject *parent)
    : QObject(parent)
{
}

EventPubSub::~EventPubSub()
{
    clearAll();
}

EventPubSub* EventPubSub::instance()
{
    if (!s_instance) {
        QMutexLocker locker(&s_instanceMutex);
        if (!s_instance) {
            s_instance = new EventPubSub();
        }
    }
    return s_instance;
}

bool EventPubSub::subscribe(const QString &topicId, 
                            QObject *receiver, 
                            std::function<void(const QVariant &)> callback)
{
    if (!receiver || !callback) {
        qWarning() << "EventPubSub::subscribe: Invalid receiver or callback";
        return false;
    }
    
    QMutexLocker locker(&m_mutex);
    
    SubscriberInfo info(receiver, callback);
    
    // 检查是否已经订阅
    if (m_subscribers.contains(topicId)) {
        QList<SubscriberInfo> &subscribers = m_subscribers[topicId];
        for (const SubscriberInfo &existing : subscribers) {
            if (existing == info) {
                qWarning() << "EventPubSub::subscribe: Already subscribed to topic:" << topicId;
                return false;
            }
        }
    }
    
    // 添加到订阅列表
    m_subscribers[topicId].append(info);
    
    qDebug() << "EventPubSub: Subscribed to topic" << topicId 
             << "with receiver" << receiver;
    
    return true;
}

bool EventPubSub::unsubscribe(const QString &topicId, QObject *receiver)
{
    if (!receiver) {
        qWarning() << "EventPubSub::unsubscribe: Invalid receiver";
        return false;
    }
    
    QMutexLocker locker(&m_mutex);
    
    if (!m_subscribers.contains(topicId)) {
        qWarning() << "EventPubSub::unsubscribe: Topic not found:" << topicId;
        return false;
    }
    
    QList<SubscriberInfo> &subscribers = m_subscribers[topicId];
    int removedCount = 0;
    
    // 移除所有匹配的订阅者
    for (int i = subscribers.size() - 1; i >= 0; --i) {
        if (subscribers[i].receiver == receiver) {
            subscribers.removeAt(i);
            removedCount++;
        }
    }
    
    // 如果列表为空,移除主题
    if (subscribers.isEmpty()) {
        m_subscribers.remove(topicId);
    }
    
    // 检查该receiver是否还有其他订阅
    bool hasOtherSubscriptions = false;
    if (removedCount > 0) {
        for (const QList<SubscriberInfo> &subList : m_subscribers.values()) {
            for (const SubscriberInfo &info : subList) {
                if (info.receiver == receiver) {
                    hasOtherSubscriptions = true;
                    break;
                }
            }
            if (hasOtherSubscriptions) {
                break;
            }
        }
        
        // 如果没有其他订阅,清理辅助对象
        if (!hasOtherSubscriptions && m_receiverHelpers.contains(receiver)) {
            ReceiverHelper *helper = m_receiverHelpers[receiver];
            m_receiverHelpers.remove(receiver);
            helper->deleteLater();
        }
        
        qDebug() << "EventPubSub: Unsubscribed from topic" << topicId 
                 << "for receiver" << receiver;
    }
    
    return removedCount > 0;
}

int EventPubSub::unsubscribeAll(QObject *receiver)
{
    if (!receiver) {
        return 0;
    }
    
    QMutexLocker locker(&m_mutex);
    
    int totalRemoved = 0;
    QStringList topicsToRemove;
    
    // 遍历所有主题
    for (auto it = m_subscribers.begin(); it != m_subscribers.end(); ++it) {
        QList<SubscriberInfo> &subscribers = it.value();
        int removedCount = 0;
        
        // 移除所有匹配的订阅者
        for (int i = subscribers.size() - 1; i >= 0; --i) {
            if (subscribers[i].receiver == receiver) {
                subscribers.removeAt(i);
                removedCount++;
            }
        }
        
        totalRemoved += removedCount;
        
        // 如果列表为空,标记为删除
        if (subscribers.isEmpty()) {
            topicsToRemove.append(it.key());
        }
    }
    
    // 移除空主题
    for (const QString &topic : topicsToRemove) {
        m_subscribers.remove(topic);
    }
    
    // 清理辅助对象(如果没有其他订阅了)
    if (totalRemoved > 0 && m_receiverHelpers.contains(receiver)) {
        ReceiverHelper *helper = m_receiverHelpers[receiver];
        m_receiverHelpers.remove(receiver);
        helper->deleteLater();
    }
    
    if (totalRemoved > 0) {
        qDebug() << "EventPubSub: Unsubscribed all topics for receiver" << receiver
                 << "Total:" << totalRemoved;
    }
    
    return totalRemoved;
}

void EventPubSub::publish(const QString &topicId, 
                         const QVariant &data, 
                         PublishType type)
{
    QMutexLocker locker(&m_mutex);
    
    if (!m_subscribers.contains(topicId)) {
        qDebug() << "EventPubSub::publish: No subscribers for topic:" << topicId;
        return;
    }
      
    // 复制订阅者列表(避免在回调中持有锁)
    QList<SubscriberInfo> subscribers = m_subscribers[topicId];
    locker.unlock();
    
    // 根据发布类型执行回调
    for (const SubscriberInfo &subscriber : subscribers) {
        // 检查接收者是否仍然有效
        if (!subscriber.receiver) {
            continue;
        }
        
        switch (type) {
        case PublishType::Sync:
            executeCallbackSync(subscriber, data);
            break;
            
        case PublishType::AsyncCurrent:
            executeCallbackAsyncCurrent(subscriber, data);
            break;
            
        case PublishType::AsyncReceiver:
            executeCallbackAsyncReceiver(subscriber, data);
            break;
        }
    }
}

void EventPubSub::executeCallbackSync(const SubscriberInfo &subscriber, const QVariant &data)
{
    // 同步执行:直接在当前线程调用
    if (subscriber.callback) {
        subscriber.callback(data);
    }
}

void EventPubSub::executeCallbackAsyncCurrent(const SubscriberInfo &subscriber, const QVariant &data)
{
    // 异步当前线程:通过事件循环执行
    if (!subscriber.callback) {
        return;
    }
    
    // 创建回调事件
    CallbackEvent *event = new CallbackEvent(subscriber.callback, data);
    
    // 投递到当前线程的事件队列
    QCoreApplication::postEvent(static_cast<QObject*>(this), event);
}

QObject* EventPubSub::getOrCreateReceiverHelper(QObject *receiver)
{
    if (!receiver) {
        return nullptr;
    }
    
    QMutexLocker locker(&m_mutex);
    
    // 检查是否已存在辅助对象
    if (m_receiverHelpers.contains(receiver)) {
        return m_receiverHelpers[receiver];
    }
    
   // 创建新的辅助对象(不设置父对象,避免跨线程问题)
    // 先创建对象,然后移动到接收者线程,这样就不会有跨线程父子关系的问题
    ReceiverHelper *helper = new ReceiverHelper(nullptr);
    helper->moveToThread(receiver->thread());
    m_receiverHelpers[receiver] = helper;
    
    return helper;
}

void EventPubSub::executeCallbackAsyncReceiver(const SubscriberInfo &subscriber, const QVariant &data)
{
    // 异步接收者:在接收者对象所在线程的事件循环中执行
    if (!subscriber.callback || !subscriber.receiver) {
        return;
    }
    
    // 检查接收者对象是否仍然有效
    if (!subscriber.receiver->thread()) {
        return;
    }
    
    // 获取或创建辅助对象
    QObject *helper = getOrCreateReceiverHelper(subscriber.receiver);
    if (!helper) {
        return;
    }
    
    // 创建回调事件
    CallbackEvent *event = new CallbackEvent(subscriber.callback, data);
    
    // 投递到辅助对象所在线程的事件队列(与接收者同一线程)
    QCoreApplication::postEvent(helper, event);
}

bool EventPubSub::event(QEvent *e)
{
    if (e->type() == CallbackEvent::EventType) {
        CallbackEvent *callbackEvent = static_cast<CallbackEvent *>(e);
        callbackEvent->execute();
        return true;
    }
    
    // 调用基类的event方法
    return QObject::event(e);
}

int EventPubSub::getSubscriberCount(const QString &topicId) const
{
    QMutexLocker locker(&m_mutex);
    
    if (m_subscribers.contains(topicId)) {
        return m_subscribers[topicId].size();
    }
    
    return 0;
}

QStringList EventPubSub::getAllTopics() const
{
    QMutexLocker locker(&m_mutex);
    return m_subscribers.keys();
}

void EventPubSub::clearAll()
{
    QMutexLocker locker(&m_mutex);
    
    // 清理所有辅助对象
    for (ReceiverHelper *helper : m_receiverHelpers.values()) {
        helper->deleteLater();
    }
    m_receiverHelpers.clear();
    
    // 清理所有订阅
    m_subscribers.clear();
    
    qDebug() << "EventPubSub: Cleared all subscriptions";
}

核心组件

  1. EventPubSub类:事件发布订阅系统的核心类
  2. SubscriberInfo结构体:存储订阅者信息(接收者对象、回调函数、线程信息)
  3. CallbackEvent类:自定义事件类,用于在事件循环中执行回调
  4. PublishType枚举:定义三种发布类型

数据结构

复制代码
Map<QString, QList<SubscriberInfo>>
  ├── Key: 主题ID(消息ID)
  └── Value: 订阅者列表
      └── SubscriberInfo
          ├── receiver: QObject指针
          ├── callback: 回调函数
          └── receiverThread: 接收者所在线程

线程安全机制

  • 使用QMutex保护订阅者映射表
  • 订阅和取消订阅操作都加锁保护
  • 发布消息时复制订阅者列表,避免长时间持有锁

技术详解

与Qt信号槽机制的关系

本组件基于Qt的事件驱动机制实现,与Qt信号槽系统有相似之处,但也有重要区别:

相似点

  • 都基于Qt的事件循环机制
  • 都支持异步执行和跨线程通信
  • 都使用事件队列实现延迟执行

区别

  • 信号槽:需要预定义信号和槽函数,编译时检查,类型安全
  • 发布订阅:动态订阅,运行时绑定,使用QVariant传递数据,更灵活

实现对比

cpp 复制代码
// Qt信号槽方式
class Sender : public QObject {
    Q_OBJECT
signals:
    void dataReady(int value);  // 需要预定义信号
};

class Receiver : public QObject {
    Q_OBJECT
public slots:
    void onDataReady(int value);  // 需要预定义槽函数
};

connect(sender, &Sender::dataReady, receiver, &Receiver::onDataReady);

// 发布订阅方式(本组件)
EventPubSub* pubSub = EventPubSub::instance();
pubSub->subscribe("topic", receiver, [](const QVariant &data) {
    // 动态回调,无需预定义
    int value = data.toInt();
});

关键技术点

1. ReceiverHelper辅助对象的设计

为什么需要辅助对象?

AsyncReceiver模式下,我们需要将回调事件投递到接收者对象所在线程的事件队列中执行。但是,直接向接收者对象投递自定义事件有一个问题:接收者对象可能没有重写event()方法来处理我们的CallbackEvent

解决方案

我们为每个接收者创建一个ReceiverHelper辅助对象,这个对象专门用来处理回调事件:

cpp 复制代码
class ReceiverHelper : public QObject {
    Q_OBJECT
protected:
    bool event(QEvent *e) override {
        if (e->type() == CallbackEvent::EventType) {
            CallbackEvent *callbackEvent = static_cast<CallbackEvent *>(e);
            callbackEvent->execute();  // 执行回调
            return true;
        }
        return QObject::event(e);
    }
};

工作流程

复制代码
发布消息(AsyncReceiver模式)
    ↓
获取或创建ReceiverHelper(与receiver同一线程)
    ↓
创建CallbackEvent事件
    ↓
投递到ReceiverHelper的事件队列
    ↓
ReceiverHelper的event()方法处理事件
    ↓
执行回调函数
2. 为什么不能用receiver作为父对象?

这是本组件设计中的一个关键技术点,涉及到Qt对象父子关系和线程安全。

问题分析

cpp 复制代码
// ❌ 错误的方式
ReceiverHelper *helper = new ReceiverHelper(receiver);  // receiver作为父对象
helper->moveToThread(receiver->thread());

为什么不能这样做?

  1. Qt的父子关系规则

    • Qt要求父子对象必须在同一个线程
    • 如果父对象和子对象在不同线程,会导致未定义行为
    • 当对象被移动到另一个线程时,其所有子对象也会被移动
  2. 线程移动的时机问题

    cpp 复制代码
    // 假设receiver在主线程,EventPubSub在另一个线程
    ReceiverHelper *helper = new ReceiverHelper(receiver);  
    // 此时helper也在EventPubSub的线程中(因为父对象receiver可能还没移动到目标线程)
    helper->moveToThread(receiver->thread());  
    // 移动helper时,Qt会检查父子关系,可能导致问题
  3. 跨线程父子关系的风险

    • 如果receiver和EventPubSub在不同线程,创建父子关系会违反Qt的线程规则
    • 可能导致对象在错误的线程中被删除
    • 可能导致事件处理在错误的线程中执行

正确的实现方式

cpp 复制代码
// ✅ 正确的方式
ReceiverHelper *helper = new ReceiverHelper(nullptr);  // 不设置父对象
helper->moveToThread(receiver->thread());  // 先移动到目标线程
m_receiverHelpers[receiver] = helper;  // 手动管理生命周期

关键要点

  1. 先创建对象,再移动线程

    • 创建时不设置父对象(使用nullptr
    • 然后调用moveToThread()移动到接收者线程
    • 这样避免了跨线程父子关系的问题
  2. 手动管理生命周期

    • 不使用Qt的父子关系自动删除
    • unsubscribe()clearAll()时手动删除辅助对象
    • 使用deleteLater()确保在正确的线程中删除
  3. 线程安全考虑

    • 辅助对象的创建和删除都在锁保护下进行
    • 确保多线程环境下的安全性

代码实现

cpp 复制代码
QObject* EventPubSub::getOrCreateReceiverHelper(QObject *receiver)
{
    if (!receiver) {
        return nullptr;
    }
    
    QMutexLocker locker(&m_mutex);
    
    // 检查是否已存在
    if (m_receiverHelpers.contains(receiver)) {
        return m_receiverHelpers[receiver];
    }
    
    // 创建新的辅助对象(不设置父对象,避免跨线程问题)
    // 先创建对象,然后移动到接收者线程,这样就不会有跨线程父子关系的问题
    ReceiverHelper *helper = new ReceiverHelper(nullptr);
    helper->moveToThread(receiver->thread());
    m_receiverHelpers[receiver] = helper;
    
    return helper;
}
3. 基于MetaCall事件的实现原理

本组件使用Qt的QEvent机制实现异步回调,这与Qt信号槽的QMetaCallEvent有相似之处。

事件类型定义

cpp 复制代码
// 自定义事件类型
QEvent::Type CallbackEvent::EventType = static_cast<QEvent::Type>(QEvent::User + 1);

事件类设计

cpp 复制代码
class CallbackEvent : public QEvent {
public:
    CallbackEvent(std::function<void(const QVariant &)> cb, const QVariant &data)
        : QEvent(EventType), m_callback(cb), m_data(data) {}
    
    void execute() {
        if (m_callback) {
            m_callback(m_data);  // 执行回调
        }
    }
    
private:
    std::function<void(const QVariant &)> m_callback;
    QVariant m_data;
};

事件处理流程

复制代码
1. 发布消息时创建CallbackEvent
    ↓
2. 通过QCoreApplication::postEvent()投递到事件队列
    ↓
3. 事件循环从队列取出事件
    ↓
4. 调用目标对象的event()方法
    ↓
5. ReceiverHelper的event()方法识别CallbackEvent
    ↓
6. 调用CallbackEvent::execute()
    ↓
7. 执行回调函数

与Qt信号槽的MetaCallEvent对比

特性 Qt信号槽 (QMetaCallEvent) 本组件 (CallbackEvent)
事件类型 Qt内部定义 自定义(QEvent::User + 1)
调用方式 通过元对象系统 通过std::function
类型检查 编译时检查 运行时检查(QVariant)
灵活性 需要预定义 动态回调
4. 线程安全的订阅管理

锁的使用策略

  1. 订阅操作:全程持有锁

    cpp 复制代码
    bool EventPubSub::subscribe(...) {
        QMutexLocker locker(&m_mutex);  // 自动加锁
        // 检查、添加订阅者
        // 锁在函数返回时自动释放
    }
  2. 发布操作:复制列表后释放锁

    cpp 复制代码
    void EventPubSub::publish(...) {
        QMutexLocker locker(&m_mutex);
        QList<SubscriberInfo> subscribers = m_subscribers[topicId];  // 复制
        locker.unlock();  // 立即释放锁
        
        // 在锁外执行回调,避免长时间持有锁
        for (const SubscriberInfo &subscriber : subscribers) {
            executeCallback(...);
        }
    }

为什么发布时要复制列表?

  • 避免在回调执行期间长时间持有锁
  • 防止死锁(回调函数可能再次调用subscribe/unsubscribe)
  • 提高并发性能
5. 内存管理策略

辅助对象的生命周期

  1. 创建时机:首次订阅时创建
  2. 删除时机
    • 取消订阅时检查是否还有其他订阅
    • 如果没有,删除辅助对象
    • 使用deleteLater()确保在正确的线程中删除
cpp 复制代码
// 取消订阅时的清理逻辑
if (!hasOtherSubscriptions && m_receiverHelpers.contains(receiver)) {
    ReceiverHelper *helper = m_receiverHelpers[receiver];
    m_receiverHelpers.remove(receiver);
    helper->deleteLater();  // 在正确的线程中删除
}

为什么使用deleteLater()?

  • Qt要求对象必须在创建它的线程中删除
  • deleteLater()将删除操作放入事件队列
  • 确保在正确的线程中执行删除

与Qt信号槽的深度对比

维度 Qt信号槽 本组件(发布订阅)
定义方式 编译时定义(signals/slots) 运行时动态订阅
类型安全 强类型(编译时检查) 弱类型(QVariant,运行时检查)
灵活性 需要预定义,不够灵活 动态绑定,非常灵活
性能 较高(直接调用或事件队列) 中等(事件队列+回调)
使用场景 固定的对象间通信 动态的、多对多通信
跨线程 自动处理(QueuedConnection) 自动处理(AsyncReceiver)
一对多 支持 支持
解耦程度 较高 极高(通过主题ID解耦)

适用场景选择

  • 使用Qt信号槽

    • 对象间有明确的、固定的通信关系
    • 需要编译时类型检查
    • 性能要求高
  • 使用发布订阅

    • 需要动态的、多对多通信
    • 发布者和订阅者不需要知道对方
    • 需要运行时灵活性

发布类型说明

1. Sync(同步)

在当前线程立即执行回调函数,阻塞直到回调完成。

cpp 复制代码
pubSub->publish("topic1", data, PublishType::Sync);
// 回调函数立即执行,完成后继续

适用场景

  • 需要立即得到结果的场景
  • 简单的数据处理
  • 性能要求高的场景

2. AsyncCurrent(异步当前线程)

将回调放入当前线程的事件队列,在事件循环的下一次迭代中执行。

cpp 复制代码
pubSub->publish("topic1", data, PublishType::AsyncCurrent);
// 立即返回,回调在事件循环中执行

适用场景

  • 需要延迟执行但仍在当前线程
  • 避免在同一个调用栈中递归执行
  • 批量操作后统一处理

3. AsyncReceiver(异步接收者)

将回调放入接收者对象所在线程的事件队列中执行。

cpp 复制代码
pubSub->publish("topic1", data, PublishType::AsyncReceiver);
// 立即返回,回调在接收者线程的事件循环中执行

适用场景

  • 跨线程通信
  • GUI更新(必须在主线程)
  • 线程安全的异步操作

使用方法

基本使用

cpp 复制代码
#include "eventpubsub.h"

// 获取单例实例
EventPubSub* pubSub = EventPubSub::instance();

// 创建接收者对象
QObject* receiver = new QObject();

// 订阅事件
pubSub->subscribe("topic1", receiver, [](const QVariant &data) {
    qDebug() << "Received data:" << data;
});

// 发布消息
QVariant message = QString("Hello World");
pubSub->publish("topic1", message, PublishType::AsyncReceiver);

// 取消订阅
pubSub->unsubscribe("topic1", receiver);

订阅示例

cpp 复制代码
// 方式1:使用lambda表达式
pubSub->subscribe("sensor/data", receiver, [](const QVariant &data) {
    int value = data.toInt();
    qDebug() << "Sensor value:" << value;
});

// 方式2:使用成员函数(需要包装)
class MyClass : public QObject {
    Q_OBJECT
public:
    void handleData(const QVariant &data) {
        qDebug() << "Data received:" << data;
    }
};

MyClass* obj = new MyClass();
pubSub->subscribe("topic1", obj, [obj](const QVariant &data) {
    obj->handleData(data);
});

// 方式3:使用函数对象
struct DataHandler {
    void operator()(const QVariant &data) {
        qDebug() << "Handled:" << data;
    }
};

pubSub->subscribe("topic1", receiver, DataHandler());

发布示例

cpp 复制代码
// 发布字符串
pubSub->publish("message", QVariant("Hello"), PublishType::AsyncReceiver);

// 发布整数
pubSub->publish("counter", QVariant(42), PublishType::Sync);

// 发布自定义结构(需要注册元类型)
struct MyData {
    int id;
    QString name;
};
Q_DECLARE_METATYPE(MyData)

MyData data{1, "Test"};
pubSub->publish("custom", QVariant::fromValue(data), PublishType::AsyncReceiver);

跨线程使用

cpp 复制代码
// 在工作线程中
class WorkerThread : public QThread {
protected:
    void run() override {
        EventPubSub* pubSub = EventPubSub::instance();
        msleep(1000);
        qWarning()<< "publish threadId:"<<QThread::currentThreadId();
        // 发布消息(自动在接收者线程执行)
        pubSub->publish("work/done", QVariant("Result"), 
                       PublishType::AsyncReceiver);
    }
};

// 在主线程中
class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow() {
        EventPubSub* pubSub = EventPubSub::instance();
        
        // 订阅(回调在主线程执行)
        pubSub->subscribe("work/done", this, [this](const QVariant &data) {
            // 安全地更新GUI(在主线程中)
            statusLabel->setText(data.toString());
            qWarning()<< "recv threadId:"<<QThread::currentThreadId();
            qWarning()<<"msg"<<data.toString();
        });
        
        // 启动工作线程
        WorkerThread* worker = new WorkerThread();
        worker->start();
    }
};

API参考

EventPubSub类

静态方法
cpp 复制代码
static EventPubSub* instance()

获取单例实例。

订阅方法
cpp 复制代码
bool subscribe(const QString &topicId, 
               QObject *receiver, 
               std::function<void(const QVariant &)> callback)

订阅事件。

参数

  • topicId:主题ID(消息ID)
  • receiver:订阅者对象指针
  • callback:回调函数

返回:订阅是否成功

取消订阅方法
cpp 复制代码
bool unsubscribe(const QString &topicId, QObject *receiver)

取消订阅指定主题。

cpp 复制代码
int unsubscribeAll(QObject *receiver)

取消某个对象的所有订阅。

发布方法
cpp 复制代码
void publish(const QString &topicId, 
             const QVariant &data, 
             PublishType type = PublishType::AsyncReceiver)

发布消息。

参数

  • topicId:主题ID
  • data:消息数据
  • type:发布类型(默认异步接收者)
查询方法
cpp 复制代码
int getSubscriberCount(const QString &topicId) const

获取某个主题的订阅者数量。

cpp 复制代码
QStringList getAllTopics() const

获取所有主题ID列表。

cpp 复制代码
void clearAll()

清空所有订阅。

构建说明

使用CMake

bash 复制代码
mkdir build
cd build
cmake ..
make

集成到项目

将以下文件添加到你的项目:

  • eventpubsub.h
  • eventpubsub.cpp

在CMakeLists.txt中添加:

cmake 复制代码
add_subdirectory(QtEventPubSub)
target_link_libraries(your_target QtEventPubSub)

或在.pro文件中:

pro 复制代码
SOURCES += QtEventPubSub/eventpubsub.cpp
HEADERS += QtEventPubSub/eventpubsub.h

注意事项

  1. 对象生命周期:确保订阅者对象在回调执行时仍然存在,否则可能导致崩溃
  2. 线程安全:虽然系统本身是线程安全的,但回调函数中的操作需要自行保证线程安全
  3. 性能考虑:同步模式性能最好,但会阻塞;异步模式不阻塞,但有一定开销
  4. 内存管理:组件内部使用辅助对象(ReceiverHelper)管理回调,这些对象不设置父对象,由组件自动管理生命周期
  5. 事件循环:异步模式需要目标线程有事件循环在运行
  6. 辅助对象设计:每个接收者会创建一个ReceiverHelper辅助对象,该对象不设置父对象(使用nullptr),以避免跨线程父子关系问题。这是Qt对象线程安全的重要设计决策

与ROS消息系统的对比

特性 ROS QtEventPubSub
消息类型 强类型(.msg文件) 弱类型(QVariant)
线程安全
发布模式 异步 同步/异步可选
跨进程 支持 不支持(单进程)
依赖 ROS核心 Qt Core
性能 中等 高(单进程)

最佳实践

  1. 使用有意义的主题ID :使用命名空间风格,如"sensor/temperature""control/motor"
  2. 合理选择发布类型 :根据场景选择合适的发布模式
    • 同线程快速调用:使用Sync
    • 需要延迟但仍在当前线程:使用AsyncCurrent
    • 跨线程通信:使用AsyncReceiver(默认)
  3. 及时取消订阅 :对象销毁前取消订阅,避免内存泄漏
    • 在对象的析构函数中调用unsubscribeAll()
    • 或使用QObject::destroyed信号自动清理
  4. 错误处理:在回调函数中添加错误处理逻辑
  5. 性能优化:对于高频消息,考虑批量处理或使用同步模式
  6. 与Qt信号槽的选择
    • 固定的一对一通信:优先使用Qt信号槽(类型安全、性能好)
    • 动态的多对多通信:使用发布订阅(灵活、解耦)
    • 需要运行时绑定:使用发布订阅
    • 需要编译时类型检查:使用Qt信号槽
  7. 线程安全注意事项
    • 组件本身是线程安全的
    • 但回调函数中的操作需要自行保证线程安全
    • 特别是访问共享数据时,需要额外的同步机制

示例场景

场景1:传感器数据订阅

cpp 复制代码
// 传感器发布数据
EventPubSub* pubSub = EventPubSub::instance();
pubSub->publish("sensor/temperature", QVariant(25.5), 
                PublishType::AsyncReceiver);

// 多个订阅者订阅
pubSub->subscribe("sensor/temperature", displayWidget, 
                  [](const QVariant &data) {
    // 更新显示
});

pubSub->subscribe("sensor/temperature", logger, 
                  [](const QVariant &data) {
    // 记录日志
});

场景2:命令发布

cpp 复制代码
// 发布命令
pubSub->publish("command/move", QVariant(QPoint(100, 200)), 
                PublishType::Sync);

// 订阅命令
pubSub->subscribe("command/move", robotController, 
                  [](const QVariant &data) {
    QPoint pos = data.toPoint();
    // 执行移动命令
});

场景3:状态通知

cpp 复制代码
// 状态变化通知
pubSub->publish("status/connected", QVariant(true), 
                PublishType::AsyncReceiver);

// 订阅状态变化
pubSub->subscribe("status/connected", statusBar, 
                  [](const QVariant &data) {
    bool connected = data.toBool();
    // 更新状态栏
});

许可证

本项目采用MIT许可证。

贡献

欢迎提交Issue和Pull Request。

更新日志

v1.0.0

  • 初始版本
  • 实现基本的发布订阅功能
  • 支持三种发布模式
  • 线程安全实现
相关推荐
33三 三like11 小时前
毕设任务分析
开发语言
vyuvyucd11 小时前
Linux线程编程:POSIX与C++实战指南
java·开发语言
Kratzdisteln11 小时前
【MVCD 3】
开发语言·php
癫狂的兔子11 小时前
【Python】【NumPy】random.rand和random.uniform的异同点
开发语言·python·numpy
先做个垃圾出来………11 小时前
Python整数存储与位运算
开发语言·python
leiming611 小时前
c++ find_if 算法
开发语言·c++·算法
广州服务器托管11 小时前
[2026.1.6]WINPE运维版20260106,带网络功能的PE维护系统
运维·开发语言·windows·计算机网络·个人开发·可信计算技术
a努力。11 小时前
京东Java面试被问:双亲委派模型被破坏的场景和原理
java·开发语言·后端·python·面试·linq
冰暮流星11 小时前
javascript赋值运算符
开发语言·javascript·ecmascript
谢娘蓝桥11 小时前
adi sharc c/C++ 语言指令优化
开发语言·c++