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();
        }

  

 

 

待续。。。。

posted on 2015-11-04 16:49  SimbaStar  阅读(765)  评论(0)    收藏  举报