(转)关于后台加载队列的设计

如有转载请注明:
http://www.azure.com.cn/

在游戏中,把部分的加载和合成工作,放到后台(多线程)中,是提高游戏体验,加快游戏速度的重要手段之一, 但线程的使用,却产生了很复杂的异步操作, 从此会造成空间上和时间上的,对资源,特别是独占资源的不可控制性, 一旦引入了多线程, 将大大提高了程序的出错概率和调试难度. 但是如果通过合理的设计, 有效的调度, 还是可以避免大部分的问题的.
在这里不是介绍如何创建一个线程, 这些都是很EASY的事情, 关键是要通过一种有效的设计方式, 使其安全稳定的运作起来.
下面有这样一个景像: 当你的玩家在某个地方上线以后, 这时你周围有非常多的玩家, 这是服务器会发很多创建角色的消息到你的客户端, 将你周围所有的玩家,怪物,包括你自己创建出来, 单个创建的过程, 分析下来大概包括,以下步骤:
  • 1. 创建模型
  • 2. 加载动画
  • 3. 加载纹理
  • 4. 合成纹理
  • 5. 加载装备
  • 6. 绑定装备
所以说,创建一个角色的消耗还是比较大的, 当有数百个玩家需要同时创建的时候, 如果没有任何缓冲策略的话, 轻则游戏卡住个几分钟,无反映, 重则不稳定, 程序直接就挂掉了. 就算是轻的, 我想也没有也没有那个玩家愿意卡在那里几分钟而没有反映吧!
所以,我们需要一个后台加载队列!
我们将需要处理的消息(Background Message)添加到队位, 而有一后台线程, 不停地从对首取消息, 进行处理. 我们将后台线程看做一个"永远不会停歇的工人", 只是在队列为空的时候才稍微打盹一下.他的大致的处理过程如下:
void thread()
{
 for(; ; ;)
 {
 bool rest = isBackgroundQueueEmpty(); //后台队列空了吗?
 if(rest)
 {
 sleep(2000);
 continue;
 }
 else
 sleep(100);

 ..... 加载! 加载 !
 [MARK A]
 }

}
由上伪代码可见, 此工人只有在后台队列为空时,能休息一下,但他却总不能完全停歇, 因为此线程的生命周期与整个游戏一样长.
此线程不只只限制处理一种类型的消息, 它应该能处理各种消息, 就算了一些毫无关联性的消息, 比如换装消息, 和删除消息. 当然这使我们马上想到继承.

先看下面一张图:


根类是一个BgMessage, 他只有一个接口就是getType(), 其实我们不能直接操纵它做任何事情, 它并不含有任何数据, 我们需要的是派生类, 但它可以让我们把所有的不同消息通过std::queue 来串起来.
我们这里只列举了三个消息, 创建, 删除, 和换装.
比如当我们收到创建消息以后, 可以
BgMessage* bgmsg = new CreateMessage();
bgmsg->setUid(xxxx);
bgmsg->setGender(xxxxx);
bgmsg->setRace(xxxx);
.........
queue.push_back(bgmsg);

还没有说getType()是什么意思呢, 它是返回类的标志, 标志此类是什么类.
所以上面[MARK A]的代码处,我们这样分析:
BgMessge* msg = queue.pop_front();
if(msg->getType() == CREATE_MESSAGE)
 doCreateMsg( static_cast<CreateMessage*>(msg) );
if(msg->getType() == DELETE_MESSAGE)
 doDeleteMsg( static_cast<DeleteMessage*>(msg) );
if(msg->getType() == EQUIP_MESSAGE)
 doEquipMsg( static_cast<EquipMessage*>(msg) );
delete msg;
这样抽象的消息就被具体化,
并给予不同的处理单元来处理了.
由于msg对象是开始被new出来的, 所以在消息被队首弹出来后, 需要手工delete掉.

还有一点需要注意的是, 每种不同的消息, 都是对收到此消息当前现场情况的一种封装, 所以需要使用值拷贝来把现场复制下来, 切不可直接用个指针来赋值, 因为你不知道你真正指的地方是个什么东西, 有可能只是个临时变量, 比如创建角色的名称, 以前错误的用一个指针指, 由于你真正处理到这条消息的时候, 那个字符串不知道早就被谁给delete掉了.

www.azure.com.cn
原文地址:https://www.cnblogs.com/lancidie/p/1846491.html