C ++多线程:域模型类中的显式锁

时间:2009-11-08 20:25:07

标签: c++ multithreading locking

伙计们,我正在开发一个使用C ++的多人游戏应用程序,目前正在为它选择合适的多线程架构。

应用程序的核心是无限循环,它实质上更新了游戏世界的所有实体的每一帧。目前这个World循环是单线程的。它的工作正常,但我真的希望它在多核上更具可扩展性。

由于所有World实体都存在于Locations中,并在每个框架中更新如下:

- World::update(dt) //dt is delta time since the last frame
  - Location::update(dt)
    - WorldEntity::update(dt)
    - WorldEntity::update(dt)
    - ...
  - Location::update(dt)
    - WorldEntity::update(dt)

...我在考虑在一个单独的线程中运行每个Location(及其更新逻辑)。这意味着我需要正确地同步世界实体。这就是我真的不想做的事情,因为我相信,显式锁定域类方法是错误的,它使开发,维护和调试变得更加困难。

首先,我考虑通过禁止它们之间的任何调用来隔离位置实体与不同位置中的实体。有哪些方法可以实现这一目标?将每个位置的实体存储在线程本地存储中,以便无法从外部访问它们?或者也许代替每个位置的线程而不是使用进程?(但这会使一切复杂化)。

然而,即使位置实体很好地隔离,还有另一个问题 - 持久性。我已经有了一种简单的通用持久性服务,它在一个单独的线程中运行。它可以在异步模式下使用,它接受要保存的对象并返回一个特殊的未来对象,可用于跟踪持久性进程。我很乐意使用这个服务,但是因为它在一个单独的线程中运行,我再次需要正确地同步对域类的访问。在这种情况下,可能的选择可能是实现域对象的正确克隆,以便持久性服务接受要保存的对象的副本,并且不需要显式锁定...

因此这个问题,上面所说的一切都值得吗?或者我可以简单地将显式同步逻辑添加到所有域类中并完成它吗?或者也许有一些我不知道的更好的选择?

提前致谢

更新通过Jed Smith

添加了世界结构方案

1 个答案:

答案 0 :(得分:2)

好吧,当我制作MMO游戏服务器时,我使用了Staged,一种高度并发的编程模型。您可能会在之前看到不同的高度并发编程模型。

以下是分阶段模型的一部分:

                 ...
          +-------+-------+         
          | Process Msg & |          
          |  Send AckReq  |           
          +---------------+         
          |App.MsgStage() |         
          +-------+-------+           
                  | Pop()                  
 ^              +-V-+                     
 | Events       | Q |    Msg Stage |      
 | Go Up        | 0 |   Logic-Half |        
-+------------- |   | -------------+-- ... 
 | Requests     |   |    I/O-Half  |             
 | Move Down    +-^-+              |               
 V                | Push()                            
   +--------------+-------------+                 
   |   Push OnRecv Event,       |          
   | 1 Event per message        |     
   |                            |   
   |  Epoll I/O thread for      |   
   |multi-messaging connections |  
   +------^-------^--------^----+   
          |       |        |                          
Incoming msg1    msg2     msg3         

如上图所示,它是一个网络/消息传递阶段,包括Logic-half和I / O-half。 2个半部分使用无锁队列和/或无锁环缓冲区(如事件队列和请求队列)进行通信,因此不需要锁定/互斥锁。< / p>

在制作完整的MMO时,除了服务器端的Messaging之外,还应该涉及其他阶段,例如用于加载/存储播放器的Database Stage,用于刷新怪物或资源的AI / Timer Stage,以及用于记录的Logger Stage等等。

通常,I / O的一半或下半部分负责I / O或其他耗时的任务,例如向数据库提交“select”查询,将数据写入磁盘文件或发送消息超出网络接口等,可能被阻止;逻辑半部分是纯逻辑计算,没有任何I / O操作,永远不会被阻止。

由于逻辑半部分可以非常快速地由CPU执行,所以所有Stage()(逻辑半部分),例如MsgStage()或TimerStage()都可以在一个StagedModel.Stage()调用中执行顺序< / strong>,主线程说。每个下半部分可以有一个或两个线程,比如下半线。例如,正如我们在Linux 2.6机器上测试的那样,只有一个EPOLL线程应该足以满足多个liseners和数千个消息传递客户端的需求。如果是Win / MSVC,您将使用完成端口而不是EPOLL。

这种方式对于一个完整的重量级MMO游戏服务器,你总共只有几个线程,并且它们针对多核计算机体系结构进行了优化,因为每个核心将为每个2核处理器运行两个或三个线程,或者一个每个4核处理器或两个线程。同样,您可以使用无锁队列和/或无锁环缓冲区,并且您将在分阶段模型中知道大多数队列或环缓冲区只有单个生产者和单个消费者。

因此,根据您的担忧,世界可以与舞台相关联(例如舞台舞台,AI或计时器或其他),并将其制作成一个单独的下半部分线程,仅记下所有位置的一个线程,并且应该足够。因此,您无需同时触发所有位置的更新,但如果您愿意,仍然可以执行此操作。在下半部分,例如当需要更新时,将生成更新事件(带有LocationID + WorldEntityID)的SceneStageThread和您的逻辑半部分,例如:当MainThread调用SceneStage()时,SceneStage(),OnUpdate(&amp; UpdateEvent)将处理它以更新Location和WorldEntity。如果您愿意,SceneStageThread可以生成其他事件,例如MonsterRelive Event等。

请参阅http://code.google.com/p/effonetmsg/downloads/list处的文档EffoNetMsg.pdf或http://code.google.com/p/effoaddon/downloads/list处的EffoAddons.pdf,以了解有关高度并发编程模型(包括完整的分阶段模型)和网络消息传递的更多信息;请参阅http://code.google.com/p/effocore/downloads/list处的EffoDesign_LockFree.pdf,了解有关无锁设施(如无锁队列和无锁环缓冲区)的更多信息。

Effo EDIT @ 2009nov09,添加了分阶段引用C ++接口和实现代码URL:

Interface:
http://code.google.com/p/effoaddon/source/browse/trunk/devel/effo/codebase/addons/staged/include/staged_i.h
Implementation:
http://code.google.com/p/effoaddon/source/browse/trunk/devel/effo/codebase/addons/staged/src/staged.cpp