3

我最近开始了一个制作游戏的项目(我有点新),我开始思考如何实现多线程来提高性能。

假设你在游戏中有一个单位,它有一个位置xy。这个位置是从互联网上更新的,另一个线程正在使用另一个线程x,y来渲染单元的图形(它必须知道它在哪里)。

现在假设您在这些变量上放置了一个互斥锁或 sephamore(有点不确定哪个最好用)。问题当然出在渲染线程上。你不能停下来等待,游戏会变得滞后。不过,这对于互联网线程来说不是问题。除非出现问题,否则再多几毫秒就可以更新游戏了。

所以我在想如何解决这个问题,我有了一个想法。假设您创建了 2 套x,y(从现在开始,我们只使用x它来使其更简单,但您明白了)。所以你有x1x2

  • 现在互联网线程只更新了 x1。
  • 图形线程使用 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:

现在我意识到这会占用一些额外的内存,但我认为这是值得的,至少如果你将这种技术的使用限制在关键数据片段上。

所以我的问题是:你们如何看待这个想法?也许它已经是一种常见的技术,只是我不知道它的名字。如果您对如何解决此类问题有任何其他反馈,我将不胜感激。我认为这是大多数程序员的常见问题。

4

5 回答 5

0

我喜欢这个想法,但我担心在游戏中预测动作的后果。在我看来,您最好集中精力确保您的互斥保护(共享)数据在尽可能小的部分中受到保护。例如,不要在绘图期间锁定整个对象,而是锁定,复制您需要的内容,然后解锁然后绘制。互联网方面也是如此……等待更新,获取它们,锁定,更新,解锁。

于 2013-02-20T12:36:59.837 回答
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
于 2013-02-20T12:56:07.093 回答
0

实际上,复制数据几乎正是我在某个时候(在遥远的未来)想要尝试的。一个在 C++ 中也很容易实现的想法。

如果您实现值的基本类型(DualInt、DualFloat 等)并提供所有标准运算符,则可以在内部将数据存储在二元素数组中。您的 getter 获取元素 0,而您的 setter 修改元素 1。

诀窍在于切换它们。您需要做的就是拥有一个读取器/写入器锁,您的 getter 和 setter 使用读取器部分,而您的切换器使用写入器部分。您可以让多个读者阅读(实际上是变老并设置新值),直到您想要切换。然后你获取锁的写者部分(它阻止新的读者并等待所有读者完成),并切换一个全局变量,该变量指示正在读取哪个元素以及正在写入哪个元素。

没有大惊小怪,没有麻烦,也没有复制大量的价值观。

于 2013-02-20T13:42:56.327 回答
0

在我的游戏中,我会在每一帧的末尾运行一个函数列表。

线程 1:调用服务器获取位置

线程 2:在 X,Y 处为对象渲染帧

线程 2:检查线程 1 的工作。

线程2:没有工作,继续。

线程 1:收到 X,Y!

线程 1:创建将设置最终 X,Y 的工作对象

线程 1:锁定线程 2 工作队列。

线程 1:将工作对象推送到工作队列

线程 1:解锁线程 2 工作队列。

线程 2:在 X,Y 处为对象渲染帧

线程 2:检查线程 1 的工作。

线程 2:找到工作!锁定工作队列...

线程 2:使用线程 1 工作设置对象 X、Y。

线程 2:删除工作对象

线程2:解锁工作队列

线程 2:继续渲染循环

或者,您可以等到帧渲染完成,暂停片刻,更新值,然后取消暂停渲染线程。

于 2013-02-20T12:39:38.993 回答
0

这是维护线程安全的好方法,如果您考虑双缓冲,可以对其进行扩展。在我从事的项目中,我们有一个线程安全数据库,其工作原理类似:

  • 数据有两个副本,写缓冲区和读缓冲区。
  • 每当写入数据库时​​,它都会使用写入缓冲区。
  • 当它被读取时,它使用读取缓冲区。
  • 在每一帧结束时,缓冲区被交换,因此读缓冲区现在是写缓冲区,反之亦然。

您应该能够在渲染线程中看到与双缓冲的相似之处。这使我们能够在整个项目中保持线程安全。当然,权衡是您将数据库中任何内容的内存使用量增加了一倍,因此它不适合存储大量数据。我们最大的成功通常是渲染数据和 AI 数据,它们只在一个线程中使用,所以这对我们来说不是什么大问题。

编辑:我忘了提到第二个权衡:当我们更改数据库中的值时,它直到下一帧才会生效。这对我们来说不是什么大问题,我们只需要确保在编写系统时牢记这一点。

于 2013-02-21T03:45:30.100 回答