我不知道商业游戏是如何在内部运作的,但我遇到的开源游戏似乎并没有大规模地进入线程.大多数其他桌面应用程序也是如此,通常似乎使用了两个或三个线程(例如程序逻辑和GUI更新).
为什么游戏没有很多线程?例如物理,声音,图形,AI等的单独线程?
我不知道你玩过的游戏,但大多数游戏都在一个单独的线程上运行声音.网络代码,至少套接字侦听器在单独的线程上运行.
但是,游戏引擎的其余部分通常在单个线程中运行.这是有原因的.例如,游戏中的大多数处理运行单个依赖链.图形依赖于物理引擎的状态,人工智能也是如此.设计多线程意味着您必须在各个子系统之间存在帧延迟以实现并发.如果这些子系统是每帧线性计算的,那么您可以更快地获得响应时间和更快速的游戏.从并行化中获益最多的游戏部分当然是渲染子系统,它被卸载到高度并行化的图形加速器卡上.
主要原因是,尽管听起来很优雅,但在程序中使用多个线程就像3D游戏一样复杂,真的非常非常困难.此外,在最近推出低成本多核系统之前,使用多线程并没有提供太多的性能激励.
你需要思考,线程的实际好处是什么?请记住,在单个核心机器上,线程实际上不允许并发执行,只是它的印象.在幕后,CPU在不同线程之间进行上下文切换,每次都做一点工作.因此,如果我有几个不需要等待的任务,那么同时运行它们(在单个核心上)将比线性运行它们更快.事实上,由于频繁上下文切换的额外开销,它会更慢.
如果是这种情况那么,为什么要在单个核心机器上使用线程呢?首先,因为有时任务可能需要长时间等待某些外部资源(如磁盘或其他硬件设备)才能使用.虽然任务处于等待阶段,但线程允许其他任务继续进行,从而使CPU的时间更有效率.
其次,任务可能有某种完成的最后期限,特别是如果他们正在响应事件.典型的例子是应用程序的用户界面.计算机应尽快响应用户操作事件,即使它正忙于执行其他长时间运行的任务,否则用户将变得激动并可能认为应用程序已崩溃.线程允许这种情况发生.
至于游戏,我不是游戏程序员,但我对情况的理解是这样的:3D游戏创建了游戏世界的程序化模型; 玩家,敌人,物品,地形等.此游戏世界将根据自上次更新后经过的时间量以不连续的步骤进行更新.因此,如果从最后一次游戏循环开始经过1ms,则通过使用其速度和经过时间来确定对象的位置来更新对象的位置(显然物理学比这更复杂,但是你得到了理念).其他因素(如AI和输入键)也可能有助于更新.当一切都完成后,更新的游戏世界将呈现为一个新框架,并且该过程将再次开始.此过程通常每秒发生多次.
当我们以这种方式思考游戏循环时,我们可以看到引擎实际上实现了与线程非常相似的目标.它有许多长期运行的任务(更新世界的物理,处理用户输入等),它给人的印象是它们同时发生,将它们分解成小块工作并交错这些部分,而不是依赖于CPU或操作系统来管理在每个上花费的时间,它本身就是这样做的.这意味着它可以使所有不同的任务保持正确同步,并避免真正线程带来的复杂性:锁定,抢占,重入代码等.这种方法也没有性能影响,因为我们说过单核机器无论如何都只能真正线性地执行代码.
拥有多核系统时情况会发生变化.现在,任务可以真正同时运行,并且使用线程来处理游戏世界更新的不同部分可能确实有好处,只要我们能够设法同步结果以呈现一致的帧.因此,我们期望随着多核系统的出现,游戏引擎开发人员将致力于此.事实证明,他们是.Valve是Half Life的制造商,最近在他们的Source Engine中引入了多处理器支持,我想其他许多引擎开发者也在效仿.
好吧,结果比我想象的要长一点.我不是线程或游戏专家,但我希望我没有做出任何特别明显的错误.如果我有,我相信人们会纠正我:)
如今许多游戏都使用"任务"或"工作"系统进行并行处理.也就是说,游戏产生固定数量的工作线程,用于多个任务.工作分为小块并排队,然后在工作线程可用时发送给工作线程进行处理.
这在游戏机上变得尤为常见.PS3基于Cell架构,因此您需要使用并行处理来获得系统外的最佳性能.Xbox 360可以模拟为PS3设计的任务/作业设置,因为它具有多个核心.您可能会发现大多数游戏中很多系统设计都是在360,PS3和PC代码库之间共享的,因此PC很可能使用相同的策略.
虽然很难编写线程安全代码,但正如许多其他答案所表明的那样,我认为你所看到的事情还有其他一些原因:
首先,许多开源游戏已有几年历史.特别是对于这一代控制台,如上所述,并行编程变得流行甚至是必要的.
其次,很少有开源项目似乎关注获得最高性能.正如John Carmack向Utah GLX项目指出的那样,高度优化的代码通常比未优化的代码更难维护,因此后者通常在开源上下文中是首选.
第三,我不会将游戏创建的少量线程意味着它没有很好地使用并行作业.