草庐IT

c++ - 构建和同步多线程游戏循环

coder 2024-02-08 原文

我遇到了一个关于游戏循环线程安全的轻微难题。我下面的是 3 个线程(包括主线程),它们可以一起工作。一种用于事件管理(主线程),一种用于逻辑,一种用于渲染。所有这 3 个线程都存在于它们自己的类中,如下所示。在基本测试中,该结构可以正常工作。该系统使用 SFML 并使用 OpenGL 进行渲染。

int main(){
    Gamestate gs;
    EventManager em(&gs);
    LogicManager lm(&gs);
    Renderer renderer(&gs);

    lm.start();
    renderer.start();
    em.eventLoop();

    return 0;
}

但是,正如您可能已经注意到的,我有一个“Gamestate”类,旨在充当需要在线程之间共享的所有资源的容器(主要是将 LogicManager 作为编写器,将 Renderer 作为读取器。 EventManager 主要用于窗口事件)。我的问题是:(1 和 2 是最重要的)

1) 这是处理事情的好方法吗?意思是使用“全局”Gamestate 类是个好主意吗?有更好的方法吗?

2) 我的意图是让 Gamestate 在 getters/setters 中有互斥量,除了它对读取不起作用,因为我无法在对象仍然锁定时返回它,这意味着我必须将同步放在外面getters/setters 并公开互斥量。这也意味着我将拥有大量用于所有不同资源的互斥体。解决这个问题最优雅的方法是什么?

3) 我让所有线程访问“bool run”以检查是否继续它们的循环

while(gs->run){
....
}

如果我在 EventManager 中收到退出消息,run 将设置为 false。我是否需要同步该变量?我会把它设置为 volatile 吗?

4) 不断取消引用指针等是否会对性能产生影响?例如 gs->objects->entitylist.at(2)->move();做所有那些“->”和“。”导致任何重大放缓?

最佳答案

全局状态

1) Is this a good way of going about things? Meaning is having a "global" Gamestate class a good idea to use? Is there a better way of going about it?

对于游戏而言,相对于一些可重用的代码,我认为全局状态就足够了。您甚至可以避免传递游戏状态指针,而是真正将其设为全局变量。

同步

2) My intention was to have Gamestate have mutexes in the getters/setters, except that doesn't work for reading because I can't return the object while it's still locked, which means I'd have to put synchronization outside of the getters/setters and make the mutexes public. It also means I'd have a bloody ton of mutexes for all the different resources. What is the most elegant way of going about this problem?

我会尝试从交易的角度考虑这一点。将每个状态更改包装到它自己的互斥锁定代码中不仅会影响性能,而且如果代码获取一个状态元素,对其执行一些计算并稍后设置值,而其他代码修改了该状态,则可能会导致实际上不正确的行为之间的相同元素。因此,我尝试以这样的方式构建 LogicManagerRenderer,以便将所有与 Gamestate 的交互都捆绑在几个地方。在该交互期间,线程应在状态上持有一个互斥体。

如果你想强制使用互斥锁,那么你可以在至少有两个类的地方创建一些结构。我们称它们为 GameStateDataGameStateAccessGameStateData 将包含所有状态,但不提供对其的公共(public)访问。 GameStateAccess 将是 GameStateData 的友元,并提供对其私有(private)数据的访问。 GameStateAccess 的构造函数将获取对 GameStateData 的引用或指针,并锁定该数据的互斥量。析构函数将释放互斥体。这样,您操纵状态的代码将简单地编写为一个 block ,其中 GameStateAccess 对象在范围内。

但是仍然存在一个漏洞:如果从此 GameStateAccess 类返回的对象是指向可变对象的指针或引用,那么此设置不会阻止您的代码携带此类指针受互斥量保护的范围。为防止这种情况,请注意您的编写方式,或者使用一些自定义的类似指针的模板类,一旦 GameStateAccess 超出范围就可以将其清除,或者确保您只按值传递内容不是引用。

例子

使用C++11,上述锁管理思想可以实现如下:

class GameStateData {
private:
  std::mutex _mtx;
  int _val;
  friend class GameStateAccess;
};
GameStateData global_state;

class GameStateAccess {
private:
  GameStateData& _data;
  std::lock_guard<std::mutex> _lock;
public:
  GameStateAccess(GameStateData& data)
    : _data(data), _lock(data._mtx) {}
  int getValue() const { return _data._val; }
  void setValue(int val) { _data._val = val; }
};

void LogicManager::performStateUpdate {
  int valueIncrement = computeValueIncrement(); // No lock for this computation
  { GameStateAccess gs(global_state); // Lock will be held during this scope
    int oldValue = gs.getValue();
    int newValue = oldValue + valueIncrement;
    gs.setValue(newValue); // still in the same transaction
  } // free lock on global state
  cleanup(); // No lock held here either
}

循环终止指示符

3) I have all of the threads accessing "bool run" to check if to continue their loops

while(gs->run){
....
}

run gets set to false if I receive a quit message in the EventManager. Do I need to synchronize that variable at all? Would I set it to volatile?

对于这个应用程序,一个易失但不同步的变量应该没问题。您必须将其声明为 volatile,以防止编译器生成缓存该值的代码,从而隐藏另一个线程的修改。

作为替代方案,您可能想使用 std::atomic变量。

指针间接开销

4) Does constantly dereferencing pointers and such have an impact on performance? eg gs->objects->entitylist.at(2)->move(); Do all those -> and . cause any major slowdown?

这取决于备选方案。在许多情况下,编译器将能够保留例如的值。上面代码中的gs->objects->entitylist.at(2),如果重复使用,就不用反复计算了。总的来说,我认为由于所有这些指针间接寻址而导致的性能损失是次要问题,但这很难确定。

关于c++ - 构建和同步多线程游戏循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12684844/

有关c++ - 构建和同步多线程游戏循环的更多相关文章

  1. ruby - 树顶语法无限循环 - 2

    我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

  2. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  4. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  5. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  6. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  7. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  8. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  9. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  10. ruby - Ruby 中的闭包和 for 循环 - 2

    我是Ruby的新手,有些闭包逻辑让我感到困惑。考虑这段代码:array=[]foriin(1..5)array[5,5,5,5,5]这对我来说很有意义,因为i被绑定(bind)在循环之外,所以每次循环都会捕获相同的变量。使用每个block可以解决这个问题对我来说也很有意义:array=[](1..5).each{|i|array[1,2,3,4,5]...因为现在每次通过时都单独声明i。但现在我迷路了:为什么我不能通过引入一个中间变量来修复它?array=[]foriin1..5j=iarray[5,5,5,5,5]因为j每次循环都是新的,我认为每次循环都会捕获不同的变量。例如,这绝对

随机推荐