【问题标题】:The lightest way to ignore/capture output of sub-process start from Java从Java开始忽略/捕获子进程输出的最简单方法
【发布时间】:2011-07-07 07:12:13
【问题描述】:

Java 中的子进程非常昂贵。每个进程通常由 NUMBERS 个线程支持。

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

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

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

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

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

【问题讨论】:

  • 为什么这(大量线程)是个问题?这些线程中的大多数都会“等待”某些东西,例如等待子进程写入的东西。所以这些线程不应该消耗任何 CPU。
  • 高线程数是有效的。在内存方面 - 每个线程分配它自己的堆栈。在 CPU 方面 - 上下文切换会降低您的 CPU 能力。而且每个线程都带有锁,这是程序员必须关心的对象。

标签: java multithreading subprocess


【解决方案1】:

我创建了一个开源库,它允许在 java 和您的子进程之间进行非阻塞 I/O。该库提供了一个事件驱动的回调模型。它依赖于 JNA 库来使用特定平台的原生 API,例如 Linux 上的 epoll、MacOS X 上的 kqueue/kevent 或 Windows 上的 IO Completion Ports。

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

https://github.com/brettwooldridge/NuProcess

【讨论】:

    【解决方案2】:

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

    但坏消息是他们忘记提供“Redirect.toNull”,这意味着您将要执行“if(*nix)/dev/null elsif(win)nil”之类的操作

    令人难以置信的是,仍然缺少 NIO/2 api for Process;但我认为 redirectOutput+NIO2 的 AsynchronizeChannel 会有所帮助。

    【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 它让事情变得复杂,难以调试,几乎无法做多线程
    【解决方案4】:

    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 个线程来读取流程输入流,您可以简单地忽略输入如果您没有向其写入任何内容,请流式传输。

    【讨论】:

    • 我以前也想过。但它有一个很大的错误——它不会关闭流也不会释放 GC。可能需要额外使用 SoftReference 或最终进行垃圾引用收集。对于应用程序中的小工具来说,有些东西太多了。
    • 让我想想......我必须从你的代码中改进的是 - 1. Weak/SoftReference; 2.一个并发锁来代替Thread.sleep; 3. 使用 AOP 或模式来捕获 waitFor() 和 destory()。 4. 确保速度很快。 5. 提交给Apache Common-IO、Google-IO、OpenJDK,让他们做维护。
    • StreamSwallower 类不会自行关闭流,我在答案中添加了一个 ProcessWatcher 类以显示您将如何管理进程/流。我认为不需要弱/软引用。使用上面的代码,您有 2 个线程管理许多进程。
    【解决方案5】:

    您不需要任何额外的线程来在 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;
        }
      }
    }
    

    您也可以像在Is it possible to read from a InputStream with a timeout? 中那样使用反射来从Process.getInputStream() 获得一个NIO FileChannel,但是您必须担心不同的JDK 版本以换取摆脱轮询。

    【讨论】:

      【解决方案6】:

      为了回答你的话题(我不明白描述),我假设你的意思是 shell 子进程输出,检查这些 SO 问题:

      platform-independent /dev/null output sink for Java

      Is there a Null OutputStream in Java?

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

      command > /dev/null 2>&1
      

      【讨论】:

      • 听起来很棒,但实际上它创造了更多。 ">" 不是一个有效的命令,而是 bash/cmd 的一个 shell 功能。您必须为目标进程的输出重定向启动一个包装器 bash 进程。
      • 对,您可以更轻松地创建网关 bash 脚本,该脚本接收带有参数的命令并处理输出。
      【解决方案7】:

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

      【讨论】:

      • 这些脚本语言也有针对 JVM 的实现,可以方便与现有 Java 代码的集成
      • 没有。我不是。它们是根据应用程序的需要调用的,而不是 shell 编程。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-21
      相关资源
      最近更新 更多