我最近开始制作一个游戏项目(我有点新),我开始考虑如何实现多线程来提升性能。
假设您在游戏中拥有一个单位,其位置为x
和y
。这个位置是从互联网上一步更新的,另一个线程正在使用x,y
来渲染单位的图形(它必须知道它的位置)。
现在说你在这些变量上放了一个互斥锁或sephamore(有点不确定哪一个最好用)。问题当然是渲染线程。你不能停下来等待,游戏会变得迟钝。这对于互联网线程来说不是问题。除非出现问题,否则更新游戏几分钟就无所谓了。
所以我在思考如何解决这个问题,我有了一个想法。假设您创建了两组x,y
(从现在开始,只允许x
使其更简单,但您明白了)。所以你有x1
和x2
。
现在这个想法。如果x1
处于来自互联网线程的锁定中,则图形线程将只是说
“嘿,我等不及了。我会继续使用x2
,因为那是一个很好的近似位置。”
它会这样做,直到x1
再次免费。它看起来像这样。
//Thread Graphics:
if (x1 is not locked){
lock x1:
use x1
unlock x1:
x2=x1
}else{
use x2
}
//Thread Internet:
wait until x1 is unlocked:
lock x1:
save data to x1
unlock x1:
现在我意识到这将占用一些额外的内存,但我认为它值得,至少如果你将这种技术的使用仅限于关键的数据部分。
所以我的问题是:你们怎么看待这个想法?也许它已经是一种常见的技术,只有我不知道它的名字。如果您对如何解决此类问题有任何其他反馈,我将不胜感激。我认为这是大多数程序员的常见问题。
答案 0 :(得分:0)
我喜欢这个主意,但我很担心在游戏中预测行为的后果。在我看来,你最好集中精力确保你的互斥保护(共享)数据尽可能少地受到保护。例如,不是在绘图期间锁定整个对象,而是锁定,复制所需内容,然后解锁然后绘制。在互联网方面也是如此......等待更新,获取,锁定,更新,解锁。
答案 1 :(得分:0)
在我的游戏中,我在每个帧结束时运行一系列函数。
线程1:调用服务器的位置
线程2:在X,Y
处渲染Object的帧线程2:检查线程1的工作。
主题2:没有工作,继续。
线程1:收到X,Y!
线程1:创建将设置最终X,Y
的工作对象线程1:锁定线程2工作队列。
线程1:将工作对象推送到工作队列
线程1:解锁线程2工作队列。
线程2:在X,Y
处渲染Object的帧线程2:检查线程1的工作。
线程2:找到了工作!锁定工作队列......
线程2:使用线程1工作设置对象X,Y。
线程2:删除工作对象
线程2:解锁工作队列
线程2:继续渲染循环
或者,或者,您可以等到帧渲染完成,暂停片刻,更新值,然后取消暂停渲染线程。
答案 2 :(得分:0)
使用额外的内存并不是一个坏主意。
如果在处理数据时没有锁定x1,则可以改进算法。
而不是这个
//Thread Graphics:
if (x1 is not locked){
lock x1:
use x1
unlock x1:
x2=x1
}else{
use x2
}
使用类似的东西
//Thread Graphics:
if (x1 is not locked)
lock x1:
x2=x1
unlock x1:
use x2
答案 3 :(得分:0)
实际上,复制数据几乎就是我想在某个时候尝试的(在遥远的未来)。这个想法在C ++中也很简单。
如果实现值的基本类型(DualInt,DualFloat等)并提供所有标准运算符,则可以在内部将数据存储在双元素数组中。你的getters获取元素0,而你的setter修改元素1。
诀窍在于切换它们。您需要做的就是有一个读取器/写入器锁,您的getter和setter使用读取器部件,切换器使用writer部分。您可以让多个读者阅读(实际上变老并设置新值),直到您想要切换。然后你获取锁的编写部分(它阻止新的读者并等待所有读者完成),并切换一个全局变量,指示正在读取哪个元素以及正在写入哪个元素。
没有大惊小怪,没有麻烦,也没有复制很多价值观。
答案 4 :(得分:0)
这是一种维护线程安全的好方法,如果考虑双缓冲,可以扩展。在我参与过的项目中,我们有一个线程安全的数据库,其工作原理类似:
你应该能够在渲染线程中看到双缓冲的相似之处。这使我们能够在整个项目中保持线程安全。当然,权衡的是,您将数据库中任何内容的内存使用量增加一倍,因此它不适合存储大量数据。我们最大的点击通常是渲染数据和AI数据,这些数据和AI数据仅在单个线程中使用,所以这对我们来说不是一个大问题。
编辑:我忘了提到第二个权衡:当我们更改数据库中的值时,它将在下一帧之前生效。这对我们来说不是一个大问题,我们必须确保在编写系统时牢记这一点。