Zek*_*eke 5 c++ multithreading thread-safety
我遇到了一个关于游戏循环线程安全性的温和难题.我下面的内容是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;
}
Run Code Online (Sandbox Code Playgroud)
但是,正如您可能已经注意到我有一个"Gamestate"类,它可以充当线程之间需要共享的所有资源的容器(主要是LogicManager作为编写器,Renderer作为读者.事件管理器主要是仅适用于橱窗活动).我的问题是:(1和2是最重要的)
1)这是处理事情的好方法吗?意义是使用"全局"Gamestate类是个好主意吗?有更好的方法吗?
2)我的目的是让Gamestate在getter/setter中有互斥,除了那些不能用于读取因为我仍然无法在它仍被锁定时返回该对象,这意味着我必须将同步放在getters之外/ setters并将互斥体公之于众.这也意味着我将为所有不同的资源提供大量的互斥量.解决这个问题最优雅的方法是什么?
3)我让所有线程访问"bool run"以检查是否继续其循环
while(gs->run){
....
}
Run Code Online (Sandbox Code Playgroud)
如果我在EventManager中收到退出消息,则run设置为false.我是否需要同步该变量?我会把它设置为不稳定吗?
4)是否经常取消引用指针等对性能产生影响?例如gs-> objects-> entitylist.at(2) - > move(); 做所有那些' - >'和'.' 导致任何重大放缓?
1)这是处理事情的好方法吗?意义是使用"全局"Gamestate类是个好主意吗?有更好的方法吗?
对于游戏而言,与一些可重复使用的代码相反,我会说全球状态足够好.您甚至可以避免传递游戏状态指针,而是真正使其成为全局变量.
2)我的目的是让Gamestate在getter/setter中有互斥,除了那些不能用于读取因为我仍然无法在它仍被锁定时返回该对象,这意味着我必须将同步放在getters之外/ setters并将互斥体公之于众.这也意味着我将为所有不同的资源提供大量的互斥量.解决这个问题最优雅的方法是什么?
我试着从交易的角度考虑这个问题.将每个状态更改包装到其自己的互斥锁定代码中不仅会影响性能,而且如果代码获取一个状态元素,对其执行一些计算并稍后设置该值,则可能导致实际上不正确的行为,而其他一些代码则修改了两者之间相同的元素.因此,我尝试以这样的方式构建,LogicManager以及Renderer与Gamestate的所有交互发生在一些地方捆绑在一起.在该交互的持续时间内,线程应该在状态上持有互斥锁.
如果要强制使用互斥锁,则可以创建一些至少有两个类的构造.让我们把它们GameStateData和GameStateAccess.GameStateData将包含所有州,但不提供公共访问权限.GameStateAccess将成为GameStateData其私人数据的朋友并提供对其私人数据的访问权限.构造函数GameStateAccess将获取引用或指针GameStateData,并将锁定该数据的互斥锁.析构函数将释放互斥锁.这样,操作状态的代码将简单地写为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
}
Run Code Online (Sandbox Code Playgroud)
3)我让所有线程访问"bool run"以检查是否继续其循环
Run Code Online (Sandbox Code Playgroud)while(gs->run){ .... }如果我在EventManager中收到退出消息,则run设置为false.我是否需要同步该变量?我会把它设置为不稳定吗?
对于这个应用程序,一个易失性但非同步的变量应该没问题.您必须将其声明为volatile,以防止编译器生成缓存该值的代码,从而隐藏另一个线程的修改.
作为替代方案,您可能希望为此使用std::atomic变量.
4)是否经常取消引用指针等对性能产生影响?例如,
gs->objects->entitylist.at(2)->move();做所有这些->并.导致任何重大放缓?
这取决于替代方案.在许多情况下,gs->objects->entitylist.at(2)如果重复使用,编译器将能够保持例如上述代码中的值,并且不必一次又一次地计算它.一般来说,我会认为由于所有这个指针间接引起的性能损失是次要问题,但这很难说清楚.
class Gamestate)1)这是处理事情的好方法吗?
是.
意义是使用"全局"Gamestate类是个好主意吗?
是的,如果getter/setter是线程安全的.
有更好的方法吗?
不.数据对于游戏逻辑和表示都是必需的.如果将它放在子例程中,您可以删除全局游戏状态,但这只会将您的问题转移到另一个函数.全局Gamestate还可以让您轻松保护当前状态.
2)我的目的是让Gamestate在getter/setter中有互斥量[...].解决这个问题最优雅的方法是什么?
这称为读写器问题.您不需要公共互斥锁.请记住,你可以有很多读者,但只有一位作家.您可以为读者/编写者实现一个队列,并阻止其他读者,直到编写完成.
while(gs->run)我是否需要同步该变量?
每当变量的非同步访问可能导致未知状态时,它应该被同步.因此,如果在渲染引擎启动下一次迭代并且Gamestate已经被破坏后立即run设置为false,则会导致混乱.但是,如果gs->run仅仅是循环是否应该继续的指示,那么它是安全的.
请记住,逻辑和渲染引擎应同时停止.如果不能同时关闭两者,请先停止渲染引擎以防止冻结.
4)是否经常取消引用指针等对性能产生影响?
有两个优化规则:
编译器可能会解决这个问题.作为程序员,您应该使用最易读的版本.
| 归档时间: |
|
| 查看次数: |
1922 次 |
| 最近记录: |