游戏的实际线程处理可以开始之前,可执行几个步骤以帮助线程代码以最佳方式运行。针对功能分解线程模型对游戏进行线程处理时,这些步骤是必要的。
确定功能块
首先,确定游戏中的不同功能块。这应该是一个相当简单的过程,因为典型的游戏设计围绕着说明功能块的文档开展。此步骤包括确定块之间的交互作用和程序执行顺序,这应该会形成一个块流程图。
请注意,可能需要根据下面两部分中的步骤结果重新设计此块流程图。这些更改将在项目设计期间和实施阶段发生。
确定块之间的相关性
列出功能块及它们之间的相互连接后,必须确定所有块之间的数据相关性。此信息有助于确定是否应该复制数据组,或者是否应该同步对数据组的访问。决定这些情况时需要考虑几个因素,但主要因素是数据大小。在串行代码段中复制数据以及在并行代码段中同步数据访问可能会比较快。

(单击查看大图)
确定线程划分
可以根据数据相关性和处理器使用情况来匹配不同的代码块。请尝试公平处理,将相应的代码块置于相应的线程中。在两个或四个线程的每一个线程中放置功能的方法要使得线程几乎同时完成其执行任务。这样,一个线程在其他线程完成执行任务之前不会等待太长时间,因而不会浪费时间。
要考虑的另一个问题是正确均衡执行任务是否会导致程序花费更多时间复制数据或同步数据访问。用于访问同一数据的功能应该位于同一线程上(如果可能)。这样,它们便可以按顺序执行,而且可避免同步需要。选择的方法取决于数据访问方式。如果按顺序访问数据且处理器均衡支持将两个功能置于单独的线程上,则同步访问效果会更好。
遗憾的是,确定均衡时没有一种妥善的解决方案。为了获得最佳的可能组合,必须进行某些实验。请注意,即使线程实现并不理想,但仍然还是比让处理器浪费时间更好一点。
在考虑在多个处理器上均衡性能时,不要与特定数目的处理器进行太多连接,这一点非常重要。针对线程处理优化代码时,还应该针对未知数目的处理器对其进行优化。我们正在开发的一些芯片支持许多处理器,因此几乎无法预测未来代码可以利用多少处理器。请设计并行化方案,以将功能进行排队并在处理器可用时执行。这样一来,代码将支持系统中的 1 到 n 个处理器,其中 n 为所管理的要实现并行化的任务数。
对游戏进行线程处理
确定要并行处理的块后,便可以创建应用程序的线程。可以使用多种方法对代码进行线程处理。例如,可以使用目标操作系统提供的 API 或跨平台 API。另外,还可以选择使用 OpenMP*,该方法在编译器内提供内置线程处理支持。上述方法中最简单的一种方法是 OpenMP,此方法注重线程创建和同步。某些编译器(如英特尔® C++ 编译器)提供此支持。
OpenMP“段”

OpenMP 段指令是一种对不同的功能块快速执行线程处理的方法。图 4 中的代码段显示了将四个不同功能的执行过程分解到两个线程中的简单程度。在这里,仅用了三行代码(大括号不计算在内)对这些功能进行线程处理。
尽管这是一种执行功能线程的便捷方式,但还是有缺陷:OpenMP 创建的线程数限于定义的段组数或可用的处理器数之间较少的那一个。因此,即使该示例中的代码已在包含四个处理器的系统中运行了,也还是仅使用两个处理器。相反,如果段块比处理器多,则 OpenMP 将确定如何调度要执行的块,这可能不是最理想的。
该示例中的屏障显示应用程序在等待所有线程完成之前所处的位置。
请注意,还可以使用 OpenMP 创建不安全的线程代码,编程人员必须考虑所有线程处理条件(即使使用 OpenMP,也是如此)。
英特尔的任务队列

英特尔 C++ 编译器包括对 OpenMP 规范的扩展,允许对线程池内的任务进行排队。这样一来,如果您具备的可并行化功能数与处理器数一样多时,就可以更容易地利用系统中的所有可用处理器。
图 5 中的代码显示如何任务排队对功能进行线程处理。每个任务指令都会将相应的功能置于队列中。某个处理器可用时,就会在该处理器上执行队列中的下一个功能。
这样一来,将目标锁定在数量可变的处理器上就容易多了。但是,此方法需要更好地规划如何将功能发放到队列,因为运行并行化段的处理器数目是未知的。最终,此方法将帮助 OpenMP 代码段充分利用所有可用的处理器。
对游戏进行线程处理不是一项很难的任务。妥善规划后,可以一开始就在游戏中设计线程处理,以避免许多潜在的障碍。随着台式机系统中处理器数目的增加,线程处理将变得越来越重要。使用两个以上处理器设计线程算法的想法有助于确保在游戏中实现最大处理功率。
除了编译器中的支持 OpenMP 功能以外,英特尔还提供了多种其他工具,用于帮助简化线程处理流程。您可以在
英特尔开发人员网站中找到这些工具。