3.2 Fork/Join实现方式

Fork/Join并行框架的接口类以及实现类封装在Java.util.concurrent中。虽然Fork/Join的并行效率与其他并行框架相比较并非最优[236],但其最大优点在于给开发人员提供了非常简便的应用程序实现接口。开发人员不再需要处理各种并行相关事务,例如同步、通信等,同时可避免难以调试的死锁和数据争用等错误,只需按照给定接口的实现方式编写很少的程序即可完成算法与框架接口的对接,极大地简化了编写并发程序的琐碎工作,可大量节省开发人员的工作量,提高工作效率。Fork/Join伪代码示意图见图3.4。对于开发人员而言,应用Fork/Join并行框架实现算法并行化计算主要需关注以下4个问题。

(1)算法实现的代码类需继承Fork/Join应用接口类java.util.concurrent.RecursiveAction或者java.util.concurrent.RecursiveTask。

(2)选择合适的阈值划分任务。根据图3.2中的实例,阈值的大小直接影响子问题的分割数目。当阈值设置偏小,意味着子问题的规模更小且分割数量更多,易造成资源管理消耗过大;当阈值设置偏大,意味着子问题的规模更大且分割数量更少,甚至当子问题数量小于工作线程数时,易导致部分工作线程处于闲置状态。因此,为了保证所有工作线程均能分配到子问题,通用的阈值设置公式见式(3.1)。

img

式中:img为计算结果取整数;β为规模控制阈值;α为原问题规模;w为多核处理器逻辑线程数(具有“超线程”技术的处理器可在1个内核中模拟2个逻辑线程)。

另外,框架应用接口类提供了划分子问题的方法接口,因此,开发人员只需提供合适的阈值实现提供的接口,至于程序如何实现任务划分以及线程分配,开发人员不需要关心。

(3)实现Fork/Join接口类的void compute()方法。该方法接口主要实现各子任务的计算,即将算法可并行化部分的代码程序写入该方法接口。

(4)设置任务划分方式。每次任务划分可将父任务划分为多个子任务,而划分的子任务个数由开发人员编写代码实现。一般来说,采用对半分解父任务的方式有利于硬件实现负载均衡。

img

图3.4 Fork/Join伪代码示意图