游戏服务器之无锁队列

设计上:

(1)无锁队列(自定义消息队列)里含有一个消息缓存队列和消息数组,数组有读游标和写游标。消息缓存队列的使用用途是在数组满时使用的,数组的长度较大(102400),所以使用到消息缓存队列的情况不会很多,使用数组以及读写游标可以提供较好的随机访问速率。

(2)消息处理和派送是在逻辑线程里(会读取消息数组并移动读游标),而自定义消息队列的写入是在网络线程中(会写入消息数组并移动写游标)。

(3)自定义消息队列里的数组数据是会被读取的(处理并派送)。自定义消息队列里的队列是在数组满的时候作为消息缓存使用,会在数组有空位时写入到数组。

(4)写者与读者之间则不用互斥,依赖的判断是可读标识(volatile bool)(在自定义消息队列里的数组的成员里)。依赖volatile bool这个类型来作为互斥判断是使用了其原子特性和读内存特性。项目实践证明是可行的(包括在使用-o2 优化选项的情况下)。

(5)较好的方式是每个会话继承该队列,则队列的颗粒度是以会话为基准(一对一)。处理该队列的写者线程在同一时期只有一个,就不需要多写者之间的互斥了。

 

另外有一种较简单的关于一读一写无锁队列的实现(依赖的是读写者修改的是不同的volatile 指针)。

可参考: http://blog.csdn.net/chenjiayi_yun/article/details/8945841#comments

 

 

内容:

1、消息数组和消息队列

(1)消息数组

可读消息循环数组

 

 

#define MSG_QUEUE_SIZE 102400
MSG_STATE array_msg[MSG_QUEUE_SIZE];

 

 

 

 

消息队列(可读标识、消息(长度、地址))

typedef std::pair MSG_STATE;

消息长度 对  消息地址

typedef std::pair MSGSIZE_2_MSGADDR;

(2)消息队列

使用内存池的消息队列

std::queue > > queue_msg;

 

2、存放消息到消息数组或者队列

网络线程压入消息是需要加锁的,因为网络线程可能多个,写者之间需要互斥(写者与读者之间则不用,用的是无锁队列)。

 

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 __mt_alloc;//内存分配器(__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;
}

 

 

 

 

 

3、获取从消息数组中获取消息

 

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;
}

 

 

 

 

 

 

4、消息处理和派送

循环处理完消息队列中的消息。

消息处理和派送是在逻辑线程里,而消息队列的写入是在网络线程中.

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;
}

 

 

 

 

 

5、消息销毁

 

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;//移动可读下标
}

 

 

 

 

 

你可能感兴趣的:(并发)