问题:使用java调用shell脚本进行模型训练时,跑到一半就卡死,单独执行shell脚本则没有问题。
因为需求比较简单,无需等待返回结果,原执行方法如下:
private void exec(String script) throws IOException {
Runtime.getRuntime().exec(script);
}
后进行搜索,找到并修改成如下代码,死锁问题解决。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class CmdExecutor {
public void exec(String cmd) {
try {
Process proc = Runtime.getRuntime().exec(cmd);
GobblerThread errorGobbler = new GobblerThread(proc.getErrorStream(), "ERROR");
GobblerThread outputGobbler = new GobblerThread(proc.getInputStream(), "OUTPUT");
errorGobbler.start();
outputGobbler.start();
proc.waitFor();
logger.info("Exec Algorithm script: {}", cmd);
} catch (Exception e) {
e.printStackTrace();
}
}
class GobblerThread extends Thread {
InputStream is;
String type;
GobblerThread(InputStream is, String type) {
this.is = is;
this.type = type;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
原因
JDK帮助文档上这么说:如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入和从标准输入快速的读入都有可能造成子进程的所,甚至死锁。问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。
接着来分析缓冲区,哪来的这个东西,当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。 因此网上的解决方法是开两个线程在waitfor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容。
网友评论