Java调用R code有两个方式,一个是通过rJava包,通过调用R的动态链接库调用R函数,把引擎加载到内存环境,优点是代码简单,效率高,但是没有找到办法实现R里面的source()函数,所以最后放弃rJava;另一个是通过Rserve方式,Rserve方式通过连接已开启的Rserve进程,基于TCP/IP协议实现与多语言的通信。
但是使用Rserve方式,多线程的处理是个问题,因为一个Rserve服务进程同时只能有一个连接存在。在Rsession中可以做到多线程同时运行,故找来Rsession源码,学习如何多线程调用R。
Rsession中是通过开启多个Rserve服务进程来完成多线程调用R,有些细节地方值得思考和学习。
感觉不需要如Rsession中这么复杂的实现,自己简化后实现一个多线程调用R模块。
基本配置和 http://www.cnblogs.com/starRebel/p/4930825.html 一致。
启动Rserve主要包含三个步骤:1.检测环境中是否已经安装了Rserve 2.如果没有安装,则安装Rserve 3.启动Rserve服务进程。因为已经安装Rserve,所以第二步可以省略。
完整初始化代码如下:
private void init() { if (Global.R_HOME != null && (new File(Global.R_HOME)).exists()) { boolean RserveInstalled = StartRserve.isRserveInstalled(Global.R_HOME + File.separator + "bin" + File.separator + "R" + (System.getProperty("os.name").contains("Win") ? ".exe" : "")); if (!RserveInstalled) { throw new UnsupportedOperationException("Rserve service not installed"); } System.out.println("starting R daemon... "); StringBuilder RserveArgs1 = new StringBuilder("--no-save --slave"); if (rconfig.port > 0) { RserveArgs1.append(" --RS-port ").append(rconfig.port); } boolean started = StartRserve.launchRserve(Global.R_HOME + File.separator + "bin" + File.separator + "R" + (System.getProperty("os.name").contains("Win") ? ".exe" : ""), "--no-save --slave", RserveArgs1.toString(), false); if (started) { System.out.println("successfully launch Rserve"); } else { System.out.println("failed to launch Rserve"); } } else { throw new IllegalArgumentException("R_HOME environment variable not correctly set.\nYou can set it using \'java ..." + " -DR_HOME=[Path to R] ...\' startup command."); } System.out.println(threadName + " OK"); try { rConnection = new RConnection(rconfig.host, rconfig.port); rconfig.connection = rConnection; } catch (RserveException e) { e.printStackTrace(); } }
isRserveInstalled 函数来自Rsession源码。会根据操作系统的不同而调用不同系统命令,提高移植性。
细节1: 在每次Rsession类构造时会加入代码
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
Rsession.this.end();
}
});
系统正常退出,调用System.out或虚拟机被关闭时,会执行shutdownHook线程,这样可以多一层保护,确保关闭该线程开启的Rserve服务。
细节2:Rsession源代码中,RserveConf类connect()函数,在尝试连接部分会启动一个新线程连接Rserve服务进程。
RserverConf.TimeOut t = new RserverConf.TimeOut() {
protected Object defaultResult() {
return Integer.valueOf(-2);
}
protected Object command() {...}
};
try {
t.execute(CONNECT_TIMEOUT);
} catch (Exception var6) {
System.err.println(" failed: " + var6.getMessage());
}
其中execute()代码,
public synchronized void execute(long timeout) throws RserverConf.TimeOut.TimeOutException {
(new Thread(new RserverConf.TimeOut.TimeoutThread(null))).start();
try {
this.wait(timeout);
} catch (InterruptedException var4) {
if(this.getResult() == null) {
this.timedOut = true;
this.result = this.defaultResult();
throw new RserverConf.TimeOut.TimeOutException("timed out");
}
return;
}
if(this.getResult() == null) {
this.timedOut = true;
this.result = this.defaultResult();
throw new RserverConf.TimeOut.TimeOutException("timed out");
}
}
protected abstract Object defaultResult();
protected abstract Object command();
private class TimeoutThread implements Runnable {
private TimeoutThread() {
}
public void run() {
Object res = TimeOut.this.command();
RserverConf.TimeOut var2 = TimeOut.this;
synchronized(TimeOut.this) {
if(!TimeOut.this.timedOut || res == null) {
TimeOut.this.result = res;
TimeOut.this.notify();
}
}
}
}
猜测这么做的目的是防止连接过程中出现问题,超过一段时间后,就中断连接线程。这是一个不错的设计思路。出于简化考虑,暂时没有使用这个方法。
细节3:doInR()方法中,
Process x;
if(osname != null && osname.length() >= 7 && osname.substring(0, 7).equals("Windows")) {
isWindows = true;
command = "\"" + Rcmd + "\" -e \"" + todo + "\" " + rargs;
x = Runtime.getRuntime().exec(command);
} else {
command = "echo \"" + todo + "\"|" + Rcmd + " " + rargs;
x = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", command});
}
System.err.println(" executing " + command);
StreamHog error = new StreamHog(x.getErrorStream(), err != null);
StreamHog output = new StreamHog(x.getInputStream(), out != null);
if(err != null) {
error.join();
}
if(out != null) {
output.join();
}
对执行的命令的返回结果有两个输入流InputStream,如果要接收这两个输入流,最好分别启动线程接收,因为有可能中断等待输入。
Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", command})会返回一个Process对象,StreamHog是一个线程对象,读取输入流内容。
Runtime.getRuntime().exec()是另外启动一个线程执行命令,如果需要等待cmd命令执行结束再往下执行,则需要使用Process类的waitfor方法
线程池的选择,采用ExecutorService线程池,MyTask对象实现Callable接口,可以响应中断。
ps. IntelliJ idea 编译器会对while(true){} return 0; 报错,为了实现目的,改写成,
boolean flag = true;
if (flag) {
while (true) {
ItemNode itemNode = itemPool.take();
System.out.println(threadName + " get job " + itemNode.getFileName());
exexuteRcode(itemNode);
}
}
try {
rConnection.shutdown();
} catch (RserveException e) {
e.printStackTrace();
}
待续。。。。
浙公网安备 33010602011771号