多年以来,我写的服务端程序经常用到生产者、消费者模型,比如IMS的各种微服务。
讲生产者、消费者模型机制的文章太多,此处省略,本文主要是记录测试性能,权当笔记。
以模板方法实现一个生产者、消费者模型,消费者模型只是一个函数指针,该指针在模型被继承时由子类指定实际的工作线程。
工作线程的数量按需指定,通常等于CPU核数的2倍。
消息达到时,需要由指定的某个对象来处理,比如用户A发送的消息总是由A的对象
处理。
这些处理消息的对象,可能数量巨大,对象的数量将决定系统的处理容量,如果单进程计划支持N个用户,那么可分配一个大小为N的数组m_lpObjectManage_T
来管理这些对象,节点定义:
typedef struct{
LPVOID ObjectAdr; //对象的地址
THREAD_ID ThreadID; //对象对应的线程ID
INSTANCE_STATUS Status; //对象的状态,初始EMPTY,空闲FREE,工作中BUSY
} OBJ_MNG_INFO, *lpOBJ_MNG_INFO;
刚new出数组m_lpObjectManage_T
时,数组的每个节点的Status
是EMPTY,节点的ObjectAdr
都是空指针,只有当需要某个对象来处理消息时才会new出该对象,并存储在节点的ObjectAdr
,延迟分配的目的是为了降低内存占用。
每个对象在被生成时将获得一个对象ID InstanceID
。InstanceID
% ObjectNum
得到的值即为线程ID,即每个对象由哪个线程处理是在new对象时就决定的。
每个线程有自己的消息队列。
当收到一条消息时,需确认由哪个对象处理。先根据业务规则计算出对象InstanceID
(如果InstanceID
不存在,则new出新对象),根据InstanceID
计算出线程ID(前述求余方法),并派发消息到该线程的消息队列中。每个线程从各自的队列按FIFO顺序消费消息,并用关联的对象来处理消息。
在开发实际业务时,上层对象的类型各不相同,因此其构造、初始化、删除等方法需由开发者提供;同理,消息体可能各不相同,消息如何释放也须由开发者提供。
//创建对象 (由业务层提供)
virtual LPVOID CreateObjectSub( int ObjectID, LPVOID userData = nullptr) = 0;
//销毁对象 (由业务层提供)
virtual BOOL DeleteObjectSub( LPVOID lpObjAdr ) = 0;
//初始化对象 (由业务层提供)
virtual BOOL InitInstance( LPVOID lpObjAdr, LPVOID userOutput ) = 0;
//释放对象 (由业务层提供)
virtual BOOL ReleaseInstance( LPVOID lpObjAdr ) = 0;
//销毁消息 (由业务层提供)
virtual BOOL DeleteMessage( LPVOID lpMessage ) = 0;
public:
ObjThread( int ObjectType,
int ThreadMax,
int ObjectMax,
int waitTime,
int queuingNum,
int DeadlockCoredown );
BOOL RegistThread(CPA_THREADPROC ThreadAdr, char* ThreadName );
BOOL DeleteData( void );
BOOL PushMessage( LPVOID lpMessage, UINT ObjectID );
int PopMessage( THREAD_ID ThreadID,
LPVOID* lplpObjectAdr,
LPVOID* lplpMessage );
BOOL DelMessage( int ObjectID, LPVOID lpMessage, BOOL bDeleteObj );
int CreateObject( lpCREATE_OBJ_INFO lpObjectInfo = nullptr );
BOOL DeleteObject( int ObjectID );
void ReleaseAllMutex();
void ClearAllMutex();
2020-07-14注:今天在阿里云机器上意外发现性能比华为云机器有大幅提升,不得不重新补做性能测试。
测试条件:
MessageDispatch()
;typedef struct{
int num;
}TEST_MSG_BLK, *lpTEST_MSG_BLK;
3种测试方法:
在不同的测试环境下,耗时差异较大,统计如下:
测试项(1000万条消息) | 华为8核云主机1 | 阿里云2核云主机2 | 4核HP主机3 |
---|---|---|---|
单纯new和delete(测试用) | 0.8秒 = 1250万条/秒 |
0.053秒 = 18867万条/秒 |
- |
生产、不消费(测试用) | 1.5秒 = 666万条/秒 |
1.63秒 = 613万条/秒 |
- |
生产、消费(生产用) | 6.1秒 = 164万条/秒 |
2.5秒 = 400万条/秒 |
8秒 = 125万条/秒 |
- 以上表格的前2行,没有生产意义,仅用于确认线程工作的性能消耗的时间分布。
- 华为云主机比阿里云主机也差得太多了吧?!阿里云机器2核,跑4个线程;华为云机器,4~16线程的效果几乎一样,具体配置看文后注释
以上测试,工作线程在取不到消息时,将主动Sleep 2毫秒,然后再查询线程队列是否有消息。这样做一定程度上会增加CPU消耗。如果改用信号量通知,将不需要Sleep 2毫秒。
时间对比(以阿里云机器测试数据为例):
测试项 | Sleep 2毫秒 | 等待信号量(Windows) | 等待信号量(CentOS) |
---|---|---|---|
生产、消费 | 6.1秒 | 16.7秒 | 30~34秒 |
结论:采用信号量会大幅降低工作线程的消费能力。
以下修改或许可提高性能,待测:
华为云主机,8核16G Intel® Xeon® Gold 6161 CPU @ 2.20GHz ↩︎
阿里云主机,2核8G Intel® Xeon® Platinum 8163 CPU @ 2.50GHz ↩︎
HP笔记本测试,i7-6700H,4核8线程,16G内存 ↩︎