【问题标题】:C++ multithreading: explicit locks in domain model classesC++ 多线程:域模型类中的显式锁
【发布时间】:2009-11-08 20:25:06
【问题描述】:

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

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

由于所有 World 实体都存在于 Locations 中并在每一帧中更新如下:

- World::update(dt) //dt 是自上一帧以来的增量时间 - 位置::更新(dt) - WorldEntity::update(dt) - WorldEntity::update(dt) - ... - 位置::更新(dt) - WorldEntity::update(dt)

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

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

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

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

提前致谢

更新感谢 Jed Smith 添加了世界结构方案

【问题讨论】:

  • 你能想象一下你的代码结构吗?以及各个阶段和功能之间的相互作用。最重要的是您需要在代码中找到并行性。此外,请在“世界”、“位置”等术语中使用“”。你想并行化一个大循环。如果循环没有任何“循环携带的依赖”,那么它很容易。但是,就您而言,事实并非如此。然后,可以实现“管道并行”。是的,我知道有一些流行语,你可能不熟悉。但是,这是循环级并行化的一般策略。
  • 这里最好做什么?通过编辑原始帖子或回答我自己?
  • 哦,谢谢,我知道了。您还需要检查“依赖项”。例如, WorldEntity::update 是否与之前的调用有依赖关系?地点呢?了解依赖关系是并行化的第一步。
  • 在这种情况下实际上可以实现流水线。很难用简短的回答来解释。 en.wikipedia.org/wiki/Instruction_pipeline (1) 将每次迭代的大任务分成子任务。 (2) 并发执行子任务。然而,实施并不是那么容易。但是,这种并行化是最难的一种。
  • 如果 A 依赖于 B,那么执行顺序 A -> B 应该被保留。无论如何,除了持久性问题之外,可以通过调整管道并行性来并行化这样的循环。我做了几次,效果很好(四核加速 2~3 倍)。流水线并行的概念与指令流水线完全相同(参见上面的 Wiki 链接)。

标签: c++ multithreading locking


【解决方案1】:

嗯,当我制作 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         

如上图所示,它是一个Network/Messaging Stage,包括Logic-half和I/O-half。两半使用无锁队列和/或无锁环形缓冲区(例如事件队列和请求队列)进行通信,因此不需要锁/互斥锁。

在制作一个完整的MMO时,除了服务器端的消息传递之外,还应该涉及其他阶段,例如用于加载/存储玩家的数据库阶段,用于刷新怪物或资源的AI/计时器阶段,以及用于记录的记录器阶段等等.

一般来说,一个阶段的 I/O-half 或 bottom-half 负责 I/O 或其他耗时的任务,例如向数据库提交“选择”查询、将数据写入磁盘文件或发送消息可能被阻止的网络接口等;而Logic-half是纯逻辑计算,没有任何I/O操作,永远不会阻塞。

由于 Logic-half 可以很快被 CPU 执行,所有 Stage() (logic-half) 例如 MsgStage() 或 TimerStage() 都可以在一个 StagedModel.Stage() 调用中执行顺序,说主线程。每个下半部分可能有一个或两个线程,比如下半部分线程。例如,正如我们所测试的,在一台 Linux 2.6 机器上,一个 EPOLL 线程应该足以满足多监听器和数千个消息传递客户端的需求。如果是 Win/MSVC,您将使用 Completion Port 而不是 EPOLL。

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

因此,根据您的担忧,世界可以与一个舞台(例如场景舞台、人工智能或计时器或其他)相关联,并将其放在一个单独的下半部分线程中,只为您的所有位置记录一个线程,并且应该足够。因此,您不需要同时触发所有位置的更新,但如果您愿意,您仍然可以这样做。在下半部分,例如SceneStageThread, Update Event (with LocationID+WorldEntityID) 将在需要更新时生成,并且您的逻辑一半,例如SceneStage(), OnUpdate(&UpdateEvent) 将在 MainThread 调用 SceneStage() 时处理它以更新 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,添加Staged references 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

【讨论】:

  • 感谢您的链接,在回复任何有意义的内容之前,我需要一些时间来消化它们;)
  • 嗯...阅读上面的所有链接后,似乎有点混乱。如果我错了,请纠正我,但看起来 EffoNet 正在将网络与我不太喜欢的并发模型混合在一起。我已经使用 boost::asio 实现了网络,我发现它非常灵活并且完全满足我的需求。
  • 嗯,一个 Staged 由几个阶段组成,可能包含网络 I/O、磁盘 I/O、数据库 I/O 和计时器等。所以在我的回答中,网络 I/O 阶段只是一个示例阶段;也就是说,基于高并发编程模型的应用程序可能根本没有网络/消息传递 I/O 阶段。
  • 根据你的情况,如果网络部分不能成为并发模型的阶段,就让它成为它应该的样子;而其他部分,如游戏 AI/Timer 或 Logger 等可以用来组成我描述的 Staged。
猜你喜欢
  • 1970-01-01
  • 2015-11-04
  • 1970-01-01
  • 2017-11-05
  • 1970-01-01
  • 1970-01-01
  • 2010-12-25
  • 2018-06-20
  • 1970-01-01
相关资源
最近更新 更多