一个泛型(消息类型M)的消息队列实现

1. 消息队列的模式

   1)简单模式:当客户端(生产者)将消息写入到消息队列中时,消息队列中消息的数量加1,消费者实时监听消息队列,当队列中有消息时,

                则获取消息,之后执行业务逻辑,同时消息队列的消息数量减1。

      

      特点:一个生产者P发送消息到队列Q,一个消费者C接收。

   2)工作模式:由一个生产者负责消息写入队列,但是如果只有一个消费者负责消费,可能会造成消息的积压,所以准备多个消费者共同消费

                一个队列中的消息。

     

      特点:一个生产者,多个消费者,每个消费者获取到的消息唯一,多个消费者只有一个队列。

   3)发布/订阅模式(Publish/Subscribe):一个生产者发送的消息会被多个消费者获取,每个消费者得到的消息是一样的,这点不同于工作模式。

    

      topic:含义是服务端存放消息的容器

      比如微信公众号,不同的人订阅同一个公众号,收到的消息都是一样的。

      发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息。topic实现了发布和订阅,当你发布一个消息,

      所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到这个消息的拷贝。

 

2. 发布订阅模式的消息队列具体实现

   一个topic(可认为成公众号)只产生一种类型的消息,topicName(公众号名字)用来标识公众号,具有唯一性。订阅某一个公众号的所有订阅者们

   都具有相同的topicName,每个topic包含一个成员变量set来存储它们的全部订阅者。

   先贴一张整体的框架图,后续进行逐个模块分析:

   1)消息处理函数的类型抽象:当消息队列要处理消息的时候,只需请求基于这个抽象的类型所定义的对象,而无需关心具体的实现。

      M:消息类型参数化T:消息处理类类型参数化。

      很明显,想使用该类型,程序员需要提供一个类型M和消息处理类(T)。M可以是int, string,json等,当然也可以是一个类类型。

      为什么还要定义 MsgHandle 这么一个抽象接口呢?这样做的话,定义基本类型的变量时可以更简洁(只有一个参数M),抽象程度更高。

      即形如 MsgHandle<M> *handle,而不是 MsgHandle<T,M> *handle。

template <typename M>
class MsgHandle
{
public:
    MsgHandle() {}
    virtual ~MsgHandle() {}

public:
    virtual void Func(const M &) = 0;
};

template <typename T, typename M>
class MsgHandleInterface : public MsgHandle<M>
{
    typedef void (T::*Handle)(const M &);  // 指向类 T 中一个函数的指针
    
public:
    MsgHandleInterface(Handle fp, T *obj) : _handle(fp), _obj(obj) {}
    ~MsgHandleInterface() {}

public:
    void Func(const M &msg)   // 请求该类型对象中的Func函数进行消息处理
    {
        (_obj->*_handle)(msg);
    }

private:
    Handle _handle;
    T *_obj;
};

   2)消息处理线程的类型抽象:一个消息处理线程监听一个消息队列,不断从消息队列中取出消息到临时队列,然后调用传递进来的

      消息处理类的成员函数进行处理。

template <typename M>
class MsgHandleThread
{
public:
    MsgHandleThread(MsgHandle<M> *handle) : _newMsg(false), _handle(handle), _stopped(true), _stop(false) {}
    ~MsgHandleThread()
    {
        Stop();
        if(_handle != nullptr) delete _handle;
        _handle = nullptr;
    }
 
public:
    void Start()
    {
        if(_stopped == false) return;
        _stop = _stopped = false;
        _runHandle = std::thread(&MsgHandleThread::_Run, this);
    }
 
    void Stop()
    {
        std::unique_lock<std::mutex> msgLock(_msgMutex);
        _newMsg = _stop = true;
        _msgCondt.notify_all();
        _runHandle.join();
    }
 
    bool addMsg(const M &msg)
    {
        std::unique_lock<std::mutex> msgLock(_msgMutex);
        _msgQueue.push(new M(msg));
        _newMsg = true;
        _msgCondt.notify_all();
        return true;
    }
 
private:
    void _Run()
    {
        std::queue<M*> readyQueue;
        while(true)
        {
            std::unique_lock<std::mutex> msgLock(_msgMutex);
            if(!_newMsg) _msgCondt.wait(msgLock);  // wait for push
            _newMsg = false;
            if(_stop == true) break;
            while(!_msgQueue.empty())  // 将消息队列里的所有消息pop的准备队列中
            {
                readyQueue.push(_msgQueue.front());
                _msgQueue.pop();
            }
            msgLock.unlock();
            while(!readyQueue.empty())
            {
                M *msg = readyQueue.front();
                readyQueue.pop();
                if(msg == NULL) continue;
                _handle->func(*msg);
                delete msg;
            }
        }
        _stopped = true;
    }
 
private:
    bool _stop, _stopped;
    bool _newMsg;
    std::mutex _msgMutex;
    std::condition_variable _msgCondt;
    std::thread _runHandle;
    std::queue<M*> _msgQueue;
    MsgHandle<M> *_handle;
};

    3)消费者类型抽象:以微信公众号为例,每个订阅者(Subscriber)需要知道自己订阅了什么公众号(topicName)。有消息提醒时就去浏览信息(消息处理线程)。 

template <typename M>
class Subscriber
{
public:
    Subscriber(const std::string &topicName, MsgHandle<M> *handle)
        : _topicName(topicName)
        , _handleThread(new MsgHandleThread<M>(handle))  // 抽象接口只能创建指针
    {
        _handleThread->Start();
    }
    ~Subscriber()
    {
        if(_handleThread != nullptr) delete _handleThread;
        _handleThread = nullptr;
    }
    bool addMsg(const M &msg) { return _handleThread->addMsg(msg); }
    const std::string& GetTopicName() const { return _topicName; }

private:
    std::string _topicName;
    MsgHandleThread<M> *_handleThread;
};

    4)生产者类型抽象:比如微信的公众号(topic)就是一个生产消息的生产者,用 topicName 来表示一个公众号,一个公众号只可以产生一种类型的消息,

       可以有很多个人(Subscriber)进行订阅,成员变量包含一个set来存储所有的订阅者当有消息产生的时候,每个订阅者者都会收到相同的消息。

template<typename M>
class Topic
{
    typename std::set<Subscriber<M>*>::iterator iter;

public:
    Topic(const std::string &topicName) : _topicName(topicName) {}
    ~Topic()
    {
        _mtx.lock();
        for(iter = _subscribers.begin(); iter != _subscribers.end(); ++iter) 
            delete *iter;
        _mtx.unlock();
    }

public:
    Subscriber<M>* AddSubscriber(MsgHandle<M> *handle)
    {
        Subscriber<M> *subscriber = new Subscriber<M>(_topicName, handle);
        if(subscriber != nullptr)
        {
            _mtx.lock();
            _subscribers.insert(subscriber);
            _mtx.unlock();
        }
        return subscriber;
    }

    Subscriber<M>* AddSubscriber(Subscriber<M> *subscriber)
    {
        if(subscriber != nullptr && subscriber->GetTopicName() == _topicName)
        {
            _mtx.lock();
            _subscribers.insert(subscriber);
            _mtx.unlock();
        }
        return subscriber;
    }

    bool DelSubcriber(Subscriber<M> *subscriber)
    {
        if(subscriber != nullptr && _subscribers.find(subscriber) != _subscribers.end())
        {
            _mtx.lock();
            _subscribers.erase(subscriber);
            delete subscriber;
            _mtx.unlock();
        }
        return true;
    }

    bool PushMsg(const M &msg)
    {
        _mtx.lock();
        for(iter = _subscribers.begin(); iter != _subscribers.end(); ++iter)
            (*iter)->addMsg(msg);   // 每个订阅者都会收到相同的消息
        _mtx.unlock();
        return true;
    }

    const std::string& GetTopicName() const { return _topicName; }

private:
    std::string _topicName;
    std::set<Subscriber<M>*> _subscribers;   // 订阅相同消息的订阅者
    std::mutex _mtx;
};

   贴一张1-4类之间的关系图:

   

   5)订阅流程类型抽象:消费者可以通过请求这个对象订阅某个公众号,生产者通过请求这个对象进行消息发布和订阅者管理。

      类型M和topicName是一一对应的,一个topicName只会产生一种M的消息。

class Communicate
{
    typedef typename std::set<Subscriber<M>*>::iterator sIter;
    typedef typename std::vector<Topic<M>*>::iterator vIter;

public:
    // 创建新的公众号
    template <typename M>
    static bool CreateTopic(const std::string &topicName)
    {
        Communicate *instance = GetInstance();
        bool ret = false;
        if(topicName.size() == 0) return false; // invalid topic name

        instance->_busMutex.lock();
        if(instance->_topicsNames.find(topicName) != instance->_topicsNames.end()) goto _return; // topic already exist
        std::vector<Topic<M>*> *topics = static_cast<std::vector<Topic<M>*>*>(instance->_topicsIndex[typeid(M)]);
        if(topics == nullptr)   // 如果该类型的第二级索引还未建立,就创建一个vector
        {
            topics = new std::vector<Topic<M>*>;
            instance->_topicsIndex[typeid(M)] = topics;
        }

        Topic<M> *topic = new Topic<M>(topicName); // 建立一个新的公众号对象
        if(instance->_topicSubcribes.find(topicName) != instance->_topicSubcribes.end())
        {
            std::set<Subscriber<M>*> *subscribers = static_cast<std::set<Subscriber<M>*>*>(instance->_topicSubcribes[topicName]);
            for(sIter = subscribers->begin(); sIter != subscribers->end(); ++sIter)
                topic->AddSubscriber(*sIter);   // 将最初就订阅该topic的订阅者加入集合
        }
        topics->push_back(topic);
        instance->_topicsNames.insert(topicName); // 注册
        ret = true;

    _return:
        instance->_busMutex.unlock();
        return ret;
    }

    template<typename T, typename M>
    static Subscriber<M>* Subscribe(const std::string &topicName, void (T::*fp)(const M &), T *obj)
    {
        Communicate *instance = GetInstance();
        Subscriber<M> *subscriber = nullptr;
        if(topicName.size() == 0) return nullptr;  // invalid topic name

        instance->_busMutex.lock();
        if(instance->_topicsNames.find(topicName) == instance->_topicsNames.end())  // 该公众号系统还未注册
        {
            MsgHandle<M> *handle = new MsgHandleInterface<T, M>(fp, obj);
            subscriber = new Subscriber<M>(topicName, handle);
            goto _return;
        }

        if(instance->_topicsIndex.find(typeid(M)) == instance->_topicsIndex.end()) goto _return;  // topic type error
        std::vector<Topic<M>*> *topics = static_cast<std::vector<Topic<M>*>*>(instance->_topicsIndex[typeid(M)]);
        if(topics == nullptr) goto _return;  // topic type error

        for(vIter = topics->begin(); vIter != topics->end(); ++vIter)
        {
            if((*vIter)->GetTopicName() == topicName)  // 第二级索引按 topicName 查找
            {
                MsgHandle<M> *handle = new MsgHandleInterface<T, M>(fp, obj);
                subscriber = (*vIter)->AddSubscriber(handle);  // 找到对应公众号后加入订阅者
                break;
            }
        }

    _return:
        if(subscriber != nullptr)
        {
            std::set<Subscriber<M>*> *subscribers = nullptr;
            if(instance->_topicSubcribes.find(topicName) == instance->_topicSubcribes.end())
            {
                subscribers = new std::set<Subscriber<M>*>;
                instance->_topicSubcribes[topicName] = subscribers;
            }
            subscribers = static_cast<std::set<Subscriber<M>*>*>(instance->_topicSubcribes[topicName]);
            subscribers->insert(subscriber);  // 因为公众号还不存在,所以订阅者就先暂存在 _topicSubcribes,后面等产生了 topic 在进行拷贝
        }
        instance->_busMutex.unlock();
        return subscriber;
    }

    template<typename M>
    static bool UnSubscribe(Subscriber<M> *subscriber)
    {
        Communicate *instance = GetInstance();
        bool ret = false;
        if(subscriber == nullptr) return false;
        std::string topicName = subscriber->GetTopicName();
        if(topicName.size() == 0) return false; // invalid topic name

        instance->_busMutex.lock();
        if(instance->_topicsNames.find(topicName) == instance->_topicsNames.end()) goto _return; // topic already exist
        if(instance->_topicsIndex.find(typeid(M)) == instance->_topicsIndex.end()) goto _return; // topic type error
        std::vector<Topic<M>*> *topics = static_cast<std::vector<Topic<M>*>*>(instance->_topicsIndex[typeid(M)]);
        if(topics == nullptr) goto _return; // topic type error

        for(vIter = topics->begin(); vIter != topics->end(); ++vIter)
        {
            if((*vIter)->GetTopicName() == topicName) // 第二级索引按 topicName 查找
            {
                (*vIter)->DelSubcriber(subscriber);   // 找到对应公众号后删除该订阅者
                subscriber = nullptr;
                ret = true;
                break;
            }
        }

    _return:
        instance->_busMutex.unlock();
        return ret;
    }

    template<typename M>
    static bool Publish(const std::string &topicName, const M &msg)
    {
        Communicate *instance = GetInstance();
        bool ret = false;
        if(topicName.size() == 0) return false; // invalid topic name

        instance->_busMutex.lock();
        if(instance->_topicsNames.find(topicName) == instance->_topicsNames.end()) goto _return;  // topic already exist
        if(instance->_topicsIndex.find(typeid(M)) == instance->_topicsIndex.end()) goto _return;  // topic type error
        std::vector<Topic<M>*> *topics = static_cast<std::vector<Topic<M>*>*>(instance->_topicsIndex[typeid(M)]);
        if(topics == nullptr) goto _return;  // topic type error

        for(vIter = topics->begin(); vIter != topics->end(); ++vIter)
        {
            if((*vIter)->GetTopicName() == topicName)  // 第二级索引按 topicName 查找
            {
                ret = (*vIter)->PushMsg(msg);          // 找到该公众号后,发布消息,触发每个订阅者去处理
                break;
            }
        }

    _return:
        instance->_busMutex.unlock();
        return ret;
    }

private:
    Communicate() {}
    static Communicate* GetInstance()
    {
        static Communicate instance;
        return &instance;
    }
    /*
       1. _topicsIndex 和 _topicsNames 是已经存在的公众号,即系统已经注册了 topic。
       2. _topicsIndex 采用两级索引进行公众号查找,速度比直接按 topicName 查找来的快
       3. _topicSubcribes 中只是用户想订阅该公众号,即对应的 topic 不一定存在,当用户想订阅
          某公众号,但系统还没注册,就先暂存在 _topicSubcribes。
     */
    std::unordered_map<std::type_index, void*> _topicsIndex;  // map of std::pair<std::type_index, std::vector<Topic<M>*>*>
    std::unordered_map<std::string, void* > _topicSubcribes;  // 直接通过 topicName 直接得到该公众号所有订阅者。 如果有订阅者想要订阅
                                                              // 某个公众号,但系统还没有注册该公众号时,这里也会往对应的set插入一
                                                              // 个subscriber对象,即产生一个后台处理线程,当有人CreateTopic之后
                                                              // 再把这里保存的订阅者添加到topic内。
    std::set<std::string> _topicsNames;                       // 注册的公众号
    std::mutex _busMutex;
};

   如何使用这个消息队列呢?

/*
   创建公众号。
   会实例化出 MsgHandle<std::string>
             MsgHandleThread<std::string>
             Subscriber(std::string)
             Topic(std::string)
   然后产生类型为 std::string 的二级索引,如果 vector 已存在就不创建了
   产生一个名为 name 的 topic 加入vector
 */
Communicate::CreateTopic<std::string>("name");   

/*
   订阅 name 公众号,类T为自己实现的消息处理类
   然后将 _nameSubcriber 放入公众号 name 中的set集合
 */
Subscriber<std::string> *_nameSubcriber = Communicate::Subscribe("name", &T::func, this);

/*
   任意位置调用公众号发布消息的接口就可以触发订阅者的处理程序
 */
Communicate::Publish("name", "My name is Jack.");

/*
   不想触发对应的处理,就直接解除订阅即可
 */
Communicate::UnSubscribe(_nameSubcriber);

 

posted @ 2020-05-24 17:29  _yanghh  阅读(89)  评论(0编辑  收藏  举报