设计上:
(1)无锁队列(自定义消息队列)里含有一个消息缓存队列和消息数组,数组有读游标和写游标。消息缓存队列的使用用途是在数组满时使用的,数组的长度较大(102400),所以使用到消息缓存队列的情况不会很多,使用数组以及读写游标可以提供较好的随机访问速率。
(2)消息处理和派送是在逻辑线程里(会读取消息数组并移动读游标),而自定义消息队列的写入是在网络线程中(会写入消息数组并移动写游标)。
(3)自定义消息队列里的数组数据是会被读取的(处理并派送)。自定义消息队列里的队列是在数组满的时候作为消息缓存使用,会在数组有空位时写入到数组。
(4)写者与读者之间则不用互斥,依赖的判断是可读标识(volatile bool)(在自定义消息队列里的数组的成员里)。依赖volatile bool这个类型来作为互斥判断是使用了其原子特性和读内存特性。项目实践证明是可行的(包括在使用-o2 优化选项的情况下)。
(5)较好的方式是每个会话继承该队列,则队列的颗粒度是以会话为基准(一对一)。处理该队列的写者线程在同一时期只有一个,就不需要多写者之间的互斥了。
另外有一种较简单的关于一读一写无锁队列的实现(依赖的是读写者修改的是不同的volatile 指针)。
可参考: http://blog.csdn.net/chenjiayi_yun/article/details/8945841#comments
内容:
可读消息循环数组
#define MSG_QUEUE_SIZE 102400
MSG_STATE array_msg[MSG_QUEUE_SIZE];
消息队列(可读标识、消息(长度、地址))
typedef std::pair
消息长度 对 消息地址
typedef std::pair
使用内存池的消息队列
std::queue
网络线程压入消息是需要加锁的,因为网络线程可能多个,写者之间需要互斥(写者与读者之间则不用,用的是无锁队列)。
bool push_msg(const MSG::base_msg *pmsg, const uint32 cmdLen)
{
_mlock.lock();
bool bret=msgqueue.put((void*)pmsg , cmdLen);
_mlock.unlock();
return bret;
}
bool message_queue::put(const void *pmsg, const unsigned int cmdLen)
{
unsigned char *buf = __mt_alloc.allocate(cmdLen);//使用内存池分配消息缓存
if(buf)
{
bcopy(pmsg , buf , cmdLen);//把消息拷贝到缓存里
if(!putQueueToArray() && !array_msg[cursor_write].first)//把消息从消息队列里取出并拷贝到消息数组
{
//有空间的话就直接写到消息数组
array_msg[cursor_write].second.first = cmdLen; //把消息缓存指针和长度拷贝到可读消息
array_msg[cursor_write].second.second = buf;
array_msg[cursor_write].first=true;//可读标识
cursor_write = (1+cursor_write)%MSG_QUEUE_SIZE;
return true;
}
else//没有空间的话就先放到消息队列
{
queue_msg.push(std::make_pair(cmdLen , buf));
}
return true;
}
return false;
}
其中:
__gnu_cxx::__mt_alloc
把消息队列里的消息拷贝到消息循环数组
bool message_queue::putQueueToArray()
{
bool isLeft=false;
while(!queue_msg.empty())//只要消息队列是非空的就一直拷贝到可读循环组数
{
if(!array_msg[cursor_write].first)//如果消息循环数组有位置可写
{
array_msg[cursor_write].second = queue_msg.front();//获取队列头的消息到消息循环数组
array_msg[cursor_write].first=true;//可读状态
cursor_write = (1+cursor_write)%MSG_QUEUE_SIZE; //移动写游标
queue_msg.pop();//弹出队列头的消息
}
else
{
isLeft = true; //还有剩余的数据没有读把空间占满了
break;
}
}
return isLeft;
}
MSGSIZE_2_MSGADDR *message_queue::get()
{
MSGSIZE_2_MSGADDR *ret=NULL;
if(array_msg[cursor_read].first)//可读的就返回该消息
{
ret=&array_msg[cursor_read].second;
}
return ret;
}
循环处理完消息队列中的消息。
消息处理和派送是在逻辑线程里,而消息队列的写入是在网络线程中.
bool handle_msg()
{
MSGSIZE_2_MSGADDR *cmd = msgqueue.get();
while(cmd)
{
const MSG::base_msg *pmsg = (const MSG::base_msg *)cmd->second;
//消息派送
...
msgqueue.erase();
cmd = msgqueue.get();
}
if(cmd)
{
msgqueue.erase();
}
return true;
}
void message_queue::erase()
{
__mt_alloc.deallocate(array_msg[cursor_read].second.second, array_msg[cursor_read].second.first);//回收内存(地址、长度)
array_msg[cursor_read].first=false;//不可读标识(则可写)
cursor_read = (1 + cursor_read)%MSG_QUEUE_SIZE;//移动可读下标
}