一个泛型(消息类型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);
浙公网安备 33010602011771号