java - 在java : possible?中手动触发CPU缓存写回有必要吗?

标签 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"
}

消息队列将是某种双 channel 同步数据结构。也许有两个 ArrayDeque,每个都有一个锁。我相当确定这不会太麻烦。

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

  • 1.a) 工作线程在启动后是否可能会看到旧的游戏数据,因为主线程可能运行在不同的核心上,而该核心已经缓存了一些结果?
  • 1.b) 如果上述情况成立:我是否需要将游戏数据中的每个字段声明为 volatile ,以绝对保证数据不一致来保护自己?
  • 2) 如果所有字段都是 volatile 的,那么我的假设是否正确,性能会受到重大影响?
  • 3) 由于我只需要在几个且控制良好的时间点在线程之间传递数据,是否可以强制所有缓存写回主内存而不是使用 volatile 字段?
  • 4)有更好的方法吗?我的想法可能是考虑不周吗?

感谢您的帮助,并对大段文字表示歉意。我认为如果您知道预期用途,回答这个问题会更容易。

最佳答案

Since I only need to pass the data between threads at few and well controlled points in time, would it be possible to force all caches to write back to main memory instead of using volatile fields?

没有。这些都不是这样运作的。让我给你一个非常简短的答案来解释为什么你会以错误的方式思考这个问题:

1.a) Could it be that the worker thread, after being started, may see old game data because the main thread may run on a different core which has cached some of its results?

当然。或者可能是出于其他原因。内存可见性无法得到保证,因此除非您使用有保证的东西来提供内存可见性,否则您不能依赖它。

1.b) If the above is true: Would I need to declare every single field in the game data as volatile to protect myself with absolute guarantee against inconsistent data?

没有。任何确保内存可见性的方法都可以。您不必以任何特定方式执行此操作。

2) Am I right to assume that performance would take a non trivial hit if all fields are volatile?

也许吧。这可能是最糟糕的方法。

3) Since I only need to pass the data between threads at few and well controlled points in time, would it be possible to force all caches to write back to main memory instead of using volatile fields?

没有。由于不存在确保内存可见性的“将缓存写回内存”操作。您的平台甚至可能没有这样的缓存,并且问题可能完全是其他问题。您在编写 Java 代码时,无需考虑特定 CPU 的工作原理、它具有哪些核心或缓存,或者类似的事情。这是使用语义有保证且不涉及核心、缓存或类似内容的语言的一大优势。

4) Is there a better approach? Is my concept perhaps ill conceived?

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

您看到的任何谈论核心、缓存或刷新内存的 Java 文档实际上并不是在谈论真正的核心、缓存或刷新内存。它只是为您提供一些思考假设硬件的方法,以便您可以思考为什么内存可见性和总排序本身并不总是完美地工作。您的真实 CPU 或平台可能存在完全不同的问题,与这个假设的硬件没有任何相似之处。 (现实世界的 CPU 和系统具有由硬件保证的缓存一致性,并且它们的可见性/排序问题实际上完全不同!)

关于java - 在java : possible?中手动触发CPU缓存写回有必要吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54098171/

相关文章:

java - 在构造函数中调用像 Swing 的 add() 这样的可覆盖方法

java - JVM 可以缓存对象的字段吗?

java - 在这种情况下,volatile hashmap 是否足够?

C# volatile double

c# - 在 Parallel.For 中使用 Random

C++ 结构和 volatile

java - 如何发现哪个类路径条目提供了类?

java - 如何使用 HtmlUnit 显示所有 AJAX 请求

java - 部署到已经在 IntelliJ IDEA 之外运行的 Tomcat 服务器

c++ - 设计低线程争用的多线程聊天服务器