副标题#e#
假如您曾想过构建本身的超等计较机,但却对用C语言举办并行编程望而生畏,那么伪长途线程可以帮您办理这一问题。这种获奖的Java编程模子极大地简化了集群上的并行编程,并使超等计较走出尝试室,使每一位 Java 措施员都能利用它。
在已往的三年里,并行集群已在改变着超等计较的面孔。一旦代价数百万美元的单体机占了主导,并行集群很快就会成为超等计较机的选择。可以想像获得,开放源码圈内的高涨热情已导致发生了数百 — 假如不是数千的话 — 并行集群项目。第一个同时也是最著名的开放源码集群系统是 Beowulf。在NASA 赞助下,由 Thomas Sterling和Donald Becker 在1994 年宣布的Beowulf,开始是作为一个 16 节点演示集群推出的。本日,Beowulf 已有数百种实现,从Oak Ridge 国度尝试室的Stone SouperComputer到Aspen 系统公司的定制构建的贸易性集群(请参阅参考资源)。
对 Java 措施员倒霉的是,大都集群系统都是环绕基于C语言的软件动静通报 API — 如动静通报接口(MPI)或并行虚拟机(PVM)— 来实现的。用C语言举办并行编程不是件容易的事,因此我设计了一个替代方案。本文将说明如何综合运用Java 线程和 Java 长途要领挪用(RMI)来建设本身的基于Java的超等计较机。
请留意,本文假定您有 Java 线程和 RMI的应用常识。
超等计较机内有什么?
超等计较机的界说是:由八个或更多的节点构成、作为单个高机能呆板事情的集群。基于Java的超等计较机包括一个功课调治器和任意数量的运行处事器(也称为主机)。功课调治器生成多个线程,每个线程包括执行差异子任务的代码。各个线程将其代码迁移到差异的运行处事器上。然后,每个运行处事器执行迁移给它的代码并将功效返回给功课调治器。最后,功课调治器将各个线程的功效组合起来。
这种并行集群系统之所以被称为伪长途线程,是因为线程是在功课调治器上调治的,但线程内的代码却是在长途计较机上执行的。
该系统有哪些组件?
组件一词是指构成“伪长途线程”并行集群系统的逻辑模块。该系统包括以下组件:
Job dispatcher(功课调治器) 是执行节制的呆板。它生成差异的线程,每个线程都包括此集群要处理惩罚的主任务的一个子任务。每个线程内的代码都被发送到一台长途计较机去执行。线程在功课调治器上调治,所以理论上讲,该呆板不该该用于执行任何子任务。
SubTask 是一个用户界说类,该类界说主任务的一个数据或成果独立的部门。您可觉得主任务的差异部门界说差异的类。类名 SubTask 是一个示例。您可觉得一个 SubTask类取任何名字,不外这个名字应该能描写分派给它的子任务。在界说SubTask类时,您必需实现JobCodeInt接口以及 jobCode()要领,下面临其举办说明。
JobCodeInt 是一个 Java接口。您必需在界说子任务的类中实现该接口和 jobCode()要领。jobCode()要领描写了将在长途执行的代码。假如您规划在长途利用某个当地资源,您必需在jobCode()要领外部初始化这个资源。例如说,您要将一组图像发送到长途处理惩罚,就必需在jobCode()要领外部初始化 Image 工具。您可以在该要领中挪用尺度 Java 库中的类,因为长途计较机上存在这些库。
RunServer 是一个 Java 工具,该工具答允长途进程挪用其要领。它的一个要领以实现了JobCodeInt接口的工具作为参数。 RunServer 就在运行该工具的计较机(运行处事器)上执行该工具内的代码,并将计较功效作为 Object类的一个实例返回。Object 是 Java类条理布局中最高一级的类。
PseudoRemThr 是一个 Java类,该类封装了一个线程并接管给定 SubTask类的一个实例。它选择一台长途主机,并将 SubTask 实例发送到这台主机上执行。假如您要操作某台主机上可用的特定资源(诸如数据库或是打印机),则可以指定主机。
HostSelector 是一个模块。假如您没有指定长途主机,PseudoRemThr类就会挪用HostSelector 模块来选择特定的主机。假如没有空闲的主机,HostSelector 会返回负载最小的长途计较机。假如某个长途计较机是一个多处理惩罚器系统,HostSelector 大概会不止一次地返回该主机名。今朝,HostSelector 无法按照给定任务的庞洪水平来选择主机。
伪长途线程的事情方法
要利用伪长途线程,您必需实现功课调治器和运行处事器。本节将说明如何实现各个部门。
实现功课调治器
首先,将主任务解析为数据或成果独立的子任务。针对每个子任务,界说一个实现JobCodeInt接口(从而实现jobCode()要领)的类。在jobCode()要领中,界说各给定子任务要执行的代码。
#p#分页标题#e#
请留意,您不能挪用功课调治器上用户界说的的当地资源。请在该要领外部初始化所有这类资源。譬喻,您可以在SubTask类的结构函数中初始化这类资源。
建设类 PseudoRemThr的若干实例,并将 SubTask的实例通报给 PseudoRemThr的各个实例。假如您要明晰指定一台长途主机,您可以通过挪用PseudoRemThr 工具的另一个结构函数来完成。
期待这些线程完成。挪用getResult()要领来获取 PseudoRemThr的各个实例的执行功效。假如计较没有完成,功效返回一个值为 false的Boolean 工具;不然,将返回 Object类的一个实例,个中包括了计较功效。您必需将此实例转换为您所但愿的类范例。将所有的子任务功效组合为最终功效。
实现运行处事器
实现运行处事器是一项简朴的事情:
启动 RMI 注册措施。
启动 RunServer。
运行处事器在启动时接通功课调治器,并通知功课调治器它已筹备停当,可以接管要执行的任务了。
#p#副标题#e#
一个计较示例
此刻该测试这一模子了。以下计较示例利用两台计较机并行运行。一台是运行 Windows 98的333 MHz Pentium II 计较机,另一台是运行 Windows 2000 专业版的500 MHz Pentium III 计较机。
为了计较从1到10^9的所有整数的平方根之和,我建设了Sqrt类,它计较dblStart和dblEnd之间所有整数的平方根之和。
Sqrt 实现JobCodeInt接口,因此也实现了jobCode()要领。在jobCode()要领中,我界说了完成这一计较的代码。
结构函数用于将数据通报给 Sqrt类,并初始化功课调治器上的所有当地资源。必需将要计较其平方根之和的整数的起止点发送给结构函数。清单1 是 Sqrt类的界说
清单1. 界说Sqrt类
//Sqrt类计较dblStart和dblEnd之间的所有整数的平方根之和。
//计较在jobCode()要领内完成
//该类实现JobCodeInt接口,且实现代码位于jobCode()要领内
//在结构函数中将数据通报给该类,并初始化功课调治器上的当地资源。
//本例中,要计较其平方根之和的整数序列的起止点被发送给 Sqrt类
public class Sqrt implements JobCodeInt
{
double dblStart, dblEnd, dblPartialSum;
public Sqrt(double Start,double End)
{
dblStart = Start;
dblEnd = End;
}
public Object jobCode()
{
dblPartialSum = 0;
for(double i=dblStart;i<=dblEnd;i++)
//可挪用尺度的Java 函数和工具。
dblPartialSum += Math.sqrt(i);
//返回功效,一个尺度 Java类的工具。
return (new Double(dblPartialSum));
}
}
JobDispatcher类建设 Sqrt类的两个实例。然后解析主任务,将一项子任务分派给一个 Sqrt 工具(Sqrt1),并将余下的子任务分派给另一个 Sqrt 工具(Sqrt2)。接下来,JobDispatcher 建设 PseudoRemThr类的两个工具,并将 Sqrt 工具作为参数别离通报给它们。接下来就期待线程执行。
一旦线程执行完毕,就可从每个 PseudoRemThr 实例得到部门功效。将各部门功效组合起来即可获得最终功效,如清单2 所示。
清单2. 事情中的JobDispatcher
//此类可以定名为您选择的任何名称
//这里选用JobDispatcher只是为了利便
public class JobDispatcher
{
public static void main(String args[])
{
double fin = 10000000; //代表 10^9
double finByTen = fin/10; //代表 10^8
long nlStartTime = System.currentTimeMillis();
//范畴从1到3*10^8
Sqrt sqrt1 = new Sqrt(1,finByTen*3);
//范畴从((3*10^8)+1)到10^9
Sqrt sqrt2 = new Sqrt((finByTen*3)+1,fin);
//以下建设 PseudoRemThr类的两个实例。 //此结构函数的参数如下所示。 //第一个参数:代表子任务的某个类的实例
//第二个参数:执行这一子任务的长途主机
//第三个参数:PseudoRemThr 实例的描写性名称。
PseudoRemThr psr1 = new
PseudoRemThr(sqrt1,"//192.168.1.1:3333/","Win98");
PseudoRemThr psr2 = new
PseudoRemThr(sqrt2,"//192.168.1.2:3333/","Win2K");
psr1.waitForResult(); //期待执行竣事//获取每个线程的功效
Double res1 = (Double)psr1.getResult();
Double res2 = (Double)psr2.getResult();
double finalRes = res1.doubleValue() + res2.doubleValue();
long nlEndTime = System.currentTimeMillis();
System.out.println("Total time taken: " + (nlEndTime-nlStartTime));
System.out.println("Sum: " + finalRes);
}
}
机能评价
此计较的总执行时间在120,000 毫秒到 128,000 毫秒之间。假如在不解析任务的环境下在当地运行同样的任务,执行时间将在183,241到237,641 毫秒之间。
最初,主任务包罗计较从1到10^7的所有整数的平方根之和。为测试机能,我将计较范畴扩大到 10^8,最终扩大到 10^9。
#p#分页标题#e#
跟着任务量的增加,长途并行执行和当地执行所需时间的不同也越来越明明。这就是说,当执行大型任务时,长途并行执行耗损的时间较少。长途并行执行并不适合小型任务,因为呆板间通信的系统开销不容忽视。跟着任务量的增加,呆板间通信的开销与在单个呆板上执行全部任务的开销对比逐渐变得微不敷道。因此,我得出以下结论:伪长途线程系统能很好地完成需要举办大量计较的任务。
利用伪长途线程有哪些利益?
因为伪长途线程是一种基于Java的系统,它可以用于实现包括多种操纵系统的集群,或异构集群。利用伪长途线程,您就制止了转换原有 C/C++ 代码的贫苦,并且还能操作Java 尺度库及其各类扩展库。另外,伪长途线程使您不必体贴内存打点。虽然,其缺点就是系统机能与 JRE 机能直接相关。
成长偏向
此刻相当多的贸易应用措施都是用Java 平台建设的,并思量到为了操作并行性需要转换原有的C/C++ 代码的实际坚苦,此刻大概是基于Java的超等计较进入贸易规模的时候了。在开始建设基于Java的应用措施时就将并行性和负载平衡思量在内是个不错的初步。
互联网就是异构集群的一个很好的例子,因此伪长途线程可以在因特网中陈设,将 Web 转换为一个单一的、基于Java的超等计较机(要相识这一观念的细节,请参阅参考资源)。然而,从实际应用出发,您应留意到在一个专门执行单一任务的同构集群中将得到最佳功效。
最后,从日常应用出发,伪长途线程使得将局域网(LAN)– 诸如校园网和家庭网 — 转换为微型的超等计较机变得相当简朴。这就是 Beowulf 系统开创的用法。有了伪长途线程,Java编程人员也可以建设本身的超等计较机了。
参考资源
"Linux clustering cornucopia"(developerWorks, 2000 年 5 月)为您指点迷津,让您相识当前 Linux 上可用的开放源码集群办理方案和保密源码集群办理方案。
要相识关于漫衍式操纵系统的具体信息,请查察 Andrew S. Tanenbaum的Modern Operating Systems(Prentice Hall 出书公司,1992 年 2 月)。
要相识关于并行编程的具体信息,请参阅 Gregory V. Wilson的Practical Parallel Programming(麻省理工学院出书社,1995 年 12 月)。
要进一步领略集群,请参阅 Scalable Computing Laboratory的Cluster Cookbook。
有关运用Java 技能和 Web 举办超等计较的深层探讨,请参阅 Laurence Vanhelsuw的"Create your own supercomputer with Java?"(JavaWorld, 1997 年 1 月)。
Linux Documentation Project 托管着 Beowulf HOWTO 文档。
会见 Beowulf 网站,以相识 Beowulf 项目标具体信息。
请参阅关于Oak Ridge 国度尝试室著名的Stone SouperComputer的具体信息。
Aspen 系统公司是今朝提供定制集群办理方案的少数厂商之一。