在Java中手动触发CPU缓存写回:可能吗?必要?

时间:2019-01-08 19:08:52

标签: java multithreading volatile cpu-cache game-loop

我在业余时间编写视频游戏,并且在引入多线程时对数据的一致性有疑问。

目前,我的游戏是单线程的,并且具有许多教程中所述的简单的游戏循环:

while game window is not closed
{
    poll user input
    react to user input
    update game state
    render game objects
    flip buffers
}

我现在想在我的游戏中添加一个新功能,玩家可以自动执行某些繁琐而繁琐的任务,例如长距离行走(快速旅行)。我可能选择简单地将玩家角色“传送”到他们的目的地,但我不希望这么做。取而代之的是,游戏将加速运行,并且玩家角色实际上会像玩家手动进行游戏一样行走。这样做的好处是,游戏世界将像往常一样与玩家角色互动,并且任何可能发生的特殊事件仍然会发生并立即停止快速行驶。

要实现此功能,我正在考虑以下方面:

  • 启动一个新线程(工作线程),并使该线程连续更新游戏状态,直到玩家角色到达目的地为止。
  • 让主线程不再像往常一样更新游戏状态并渲染游戏对象,而是以更简单的方式显示旅行进度
  • 使用同步消息队列使主线程和工作线程通信
  • 当快速旅行结束或取消(由于玩家互动或其他原因)时,工作线程死亡,并使用主线程恢复标准游戏循环

在伪代码中,它可能看起来像这样:

[main thread]
while game window is not closed
{
    poll user input

    if user wants to cancel fast travel
    {
        write to message queue player input "cancel"
    }

    poll message queue about fast travel status

    if fast travel finished or canceled
    {
        resume regular game loop
    } else {
        render travel status
        flip buffers
    }
}

[worker thread]
while (travel ongoing)
{
    poll message queue

    if user wants to cancel fast travel
    {
        write to message queue fast travel status "canceled"
        return
    }

    update game state

    if fast travel is interrupted by internal game event
    {
        write to message queue fast travel status "canceled"
        return
    }

    write to message queue fast travel status "ongoing"
}
if travel was finished
{
    write to message queue fast travel status "finished"
}

消息队列将是某种两通道同步数据结构。也许两个ArrayDeque都有一个锁。我相当确定这不会带来太多麻烦。

我更担心的是游戏数据的缓存问题:

  • 1.a)可能是工作线程在启动之后可能会看到旧的游戏数据,因为主线程可能运行在已缓存其某些结果的其他内核上吗?
  • 1.b)如果上述情况成立:我是否需要将游戏数据中的每个字段都声明为易失性,以通过绝对保证免受数据不一致的影响来保护自己?
  • 2)我是否应该假设,如果所有字段都是不稳定的,性能将受到不小的打击?
  • 3)由于我只需要在几个时间间隔和控制良好的时间点之间在线程之间传递数据,就可以迫使所有缓存写回主存储器而不是使用易失性字段 ?
  • 4)是否有更好的方法?我的概念可能构思不当吗?

非常感谢您的帮助,也很抱歉。我认为,如果您知道预期的用途,回答这个问题会更容易。

1 个答案:

答案 0 :(得分:1)

  

由于我只需要在几个时间间隔和控制良好的时间点之间在线程之间传递数据,是否有可能强制所有缓存写回主内存而不是使用易失字段?

不。这不是这样的。让我给您简短的答案,以解释您为什么以错误的方式思考此问题:

  

1.a)可能是因为工作线程在启动后可能会看到旧的游戏数据,因为主线程可能运行在已缓存了部分结果的其他内核上吗?

好的。或可能由于其他原因。内存可见性得不到保证,因此除非您使用保证内存可见性的东西,否则您将不能依靠它。

  

1.b)如果上述情况成立:我是否需要将游戏数据中的每个字段声明为易失性,以通过绝对保证免受数据不一致的影响来保护自己?

不。任何确保内存可见性的方法都可以使用。您不必执行任何特定方式。

  

2)我是否应该假设,如果所有字段都是不稳定的,性能将受到不小的打击?

可能是。这可能是最糟糕的方法。

  

3)由于我只需要在几个时间间隔和控制良好的时间点之间在线程之间传递数据,是否有可能迫使所有缓存写回主内存而不是使用易失字段?

不。由于没有确保内存可见性的“将缓存写回内存”操作。您的平台甚至可能没有这样的缓存,而问题可能完全是其他原因。您正在编写Java代码,无需考虑特定CPU的工作方式,其具有的内核或缓存等。这是使用具有保证语义且不谈论核心,缓存或类似内容的语言的一大优势。

  

4)是否有更好的方法?我的概念可能构思不当?

绝对。您正在编写Java代码。使用各种Java同步类和函数,并依靠它们来证明它们所提供的语义。甚至不用考虑内核,缓存,刷新到内存或类似的事情。这些是硬件细节,作为Java程序员,您甚至不必考虑。

您看到的任何有关内核,缓存或刷新到内存的Java文档实际上并不是在谈论真正的内核,缓存或刷新到内存。它只是为您提供了一些有关假设性硬件的思考方式,因此您可以全神贯注地了解为什么内存可见性和总排序不能总是靠它们自己完美地运行。您的实际CPU或平台可能存在完全不同的问题,这些问题与该假设的硬件没有相似之处。 (而且现实中的CPU和系统都具有由硬件保证的缓存一致性,并且它们的可见性/顺序问题实际上完全不同)。