忽略/捕获子进程输出的最轻的方法是从Java开始

时间:2011-03-02 13:45:12

标签: java multithreading subprocess

java中的子流程非常昂贵。每个进程通常由数字线程支持。

  • 主持进程的线程(通过Linux上的JDK 1.6)
  • 读取/打印/忽略输入流的线程
  • 另一个读取/打印/忽略错误流的线程
  • 您的应用程序执行超时以及监视和终止子进程的更多线程
  • 业务逻辑线程,holduntil用于子流程返回。

如果你有一个线程focking子进程池来完成任务,那么线程的数量就会失控。因此,峰值时可能会有超过两倍的并发线程。

在许多情况下,我们分叉一个进程只是因为没有人能够编写JNI来调用JDK中缺少的本机函数(例如chmod,ln,ls),触发shell脚本等等。

可以保存一些线程,但是应该运行一些线程以防止最坏的情况(输入流上的缓冲区溢出)。

如何将Java中创建子流程的开销降至最低? 我在考虑NIO流处理,组合和共享线程,降低后台线程优先级,重用进程。但我不知道它们是否可能。

8 个答案:

答案 0 :(得分:3)

JDK7将解决此问题并在ProcessBuilder中提供新的API redirectOutput / redirectError以重定向stdout / stderr。

然而坏消息是,他们忘记提供“Redirect.toNull”,这意味着你想要做的事情就像“if(* nix)/ dev / null elsif(win)nil”

不可信的是,NIO / 2 api for Process仍然缺失;但我认为redirectOutput + NIO2的AsynchronizeChannel会有所帮助。

答案 1 :(得分:2)

我创建了一个开源库,允许java和子进程之间的非阻塞I / O.该库提供了一个事件驱动的回调模型。这取决于JNA库使用特定于平台的本机API,例如Linux上的epoll,MacOS X上的kqueue / kevent或Windows上的IO完成端口。

该项目名为NuProcess,可在此处找到:

https://github.com/brettwooldridge/NuProcess

答案 2 :(得分:1)

要回答你的主题(我不理解描述),我假设你的意思是shell子进程输出,检查这些SO问题:

platform-independent /dev/null output sink for Java

Is there a Null OutputStream in Java?

或者你可以关闭stdout和stderr来执行在Unix下执行的命令:

command > /dev/null 2>&1

答案 3 :(得分:1)

nio不起作用,因为在创建流程时,您只能访问OutputStream,而不能访问Channel。

您可以让1个线程读取多个InputStream。

类似的东西,

import java.io.InputStream;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

class MultiSwallower implements Runnable {    
   private List<InputStream> streams = new CopyOnWriteArrayList<InputStream>();
   public void addStream(InputStream s) {
       streams.add(s);
   }

   public void removeStream(InputStream s) {
       streams.remove(s);
   }

    public void run() {
        byte[] buffer = new byte[1024];
        while(true) {

          boolean sleep = true;
          for(InputStream s : streams) {
              //available tells you how many bytes you can read without blocking
              while(s.available() > 0) {
                  //do what you want with the output here
                  s.read(buffer, 0, Math.min(s.available(), 1024));
                  sleep = false;
              }   
          }
          if(sleep) {
              //if nothing is available now
              //sleep 
              Thread.sleep(50);
          }
        }

    }
}

您可以将上述类与另一个等待进程完成的类配对,例如

class ProcessWatcher implements Runnable {

    private MultiSwallower swallower = new MultiSwallower();

    private ConcurrentMap<Process, InputStream> proceses = new ConcurrentHashMap<Process, InputStream>();

    public ProcessWatcher() {

    } 

    public void startThreads() { 
        new Thread(this).start();
        new Thread(swallower).start();
    }


    public void addProcess(Process p) {
        swallower.add(p.getInputStream());
        proceses.put(p, p.getInputStream());

    }

    @Override
    public void run() {
        while(true) {

            for(Process p : proceses.keySet()) {
                try {
                    //will throw if the process has not completed
                    p.exitValue();
                    InputStream s = proceses.remove(p);
                    swallower.removeStream(s);
                } catch(IllegalThreadStateException e) { 
                    //process not completed, ignore
                }
            }
            //wait before checking again
            Thread.sleep(50);
        }
    }
}

同样,如果使用ProcessBuilder.redirectErrorStream(true),则不需要为每个错误流设置1个线程,并且您不需要1个线程来读取进程输入流,您可以简单地忽略输入如果你没有写任何东西流。

答案 4 :(得分:1)

你不需要任何额外的线程来在java中运行子进程,虽然处理超时会使事情变得复杂:

import java.io.IOException;
import java.io.InputStream;

public class ProcessTest {

  public static void main(String[] args) throws IOException {
    long timeout = 10;

    ProcessBuilder builder = new ProcessBuilder("cmd", "a.cmd");
    builder.redirectErrorStream(true); // so we can ignore the error stream
    Process process = builder.start();
    InputStream out = process.getInputStream();

    long endTime = System.currentTimeMillis() + timeout;

    while (isAlive(process) && System.currentTimeMillis() < endTime) {
      int n = out.available();
      if (n > 0) {
        // out.skip(n);
        byte[] b = new byte[n];
        out.read(b, 0, n);
        System.out.println(new String(b, 0, n));
      }

      try {
        Thread.sleep(10);
      }
      catch (InterruptedException e) {
      }
    }

    if (isAlive(process)) {
      process.destroy();
      System.out.println("timeout");
    }
    else {
      System.out.println(process.exitValue());
    }
  }

  public static boolean isAlive(Process p) {
    try {
      p.exitValue();
      return false;
    }
    catch (IllegalThreadStateException e) {
      return true;
    }
  }
}

您还可以使用[{3}}中的反射来从FileChannel获取NIO Process.getInputStream(),但是您必须担心不同的JDK版本以换取摆脱民意调查。

答案 5 :(得分:0)

由于您提及chmodlnls和shell脚本,听起来您正在尝试使用Java进行shell编程。如果是这样,您可能需要考虑更适合该任务的其他语言,例如Python,Perl或Bash。尽管在Java中创建子进程当然是可能的,但是通过它们的标准输入/输出/错误流等与它们进行交互,我认为你会发现一种脚本语言使得这种代码比Java更简洁,更容易维护。

答案 6 :(得分:0)

您是否可以使用JNA编写本机调用?

请参阅How do i programmatically change file permissions?的答案,了解chmod的一个很好的例子。

比JNI容易得多,并且比子流程快得多!

答案 7 :(得分:0)

您是否考虑过使用另一种语言(可能是shell脚本?)编写的长时间运行的辅助进程,该进程将通过stdin使用来自java的命令并执行文件操作以响应?