【问题标题】:How can I cause a child process to exit when the parent does?当父进程退出时,如何使子进程退出?
【发布时间】:2010-09-21 03:13:47
【问题描述】:

我正在使用 ProcessBuilder 启动一个子进程,如果父进程退出,则需要子进程退出。在正常情况下,我的代码正确地停止了孩子。但是,如果我导致操作系统杀死父级,子级将继续运行。

有没有办法将子进程“绑定”到父进程,这样当父进程被杀死时它就会退出?


类似问题:

【问题讨论】:

    标签: java process


    【解决方案1】:

    虽然您无法防止硬中止(例如 Unix 上的 SIGKILL),但您可以防止其他导致父进程关闭的信号(例如 SIGINT)并清理子进程。您可以通过使用关闭挂钩来完成此操作:请参阅 Runtime#addShutdownHook,以及相关的 SO 问题 here

    您的代码可能如下所示:

    String[] command;
    final Process childProcess = new ProcessBuilder(command).start();
    
    Thread closeChildThread = new Thread() {
        public void run() {
            childProcess.destroy();
        }
    };
    
    Runtime.getRuntime().addShutdownHook(closeChildThread); 
    

    【讨论】:

    • Runtime.getRuntime().addShutdownHook(new Thread(childProcess::destroy));
    • 在 Groovy 中对我来说这是Process proc = cmd.execute() addShutdownHook { proc.destroy() }
    【解决方案2】:

    子进程与其父进程之间没有联系。他们可能知道彼此的进程 ID,但他们之间没有硬性联系。你在说什么orphan process。这是操作系统级别的问题。这意味着任何解决方案都可能取决于平台。

    关于我唯一能想到的就是让孩子定期检查其父母状态,如果父母关闭则退出。不过,我不认为这会那么可靠。

    【讨论】:

    • 如果您要轮询查看子进程的 PPID 是否变为 1,则定期检查父进程的状态可能是可靠的,因为孤立进程获得了 init 进程的 PPID。 (PID=1)
    • 如果有人想知道,在 Android 系统中,当父进程死亡时,pid 似乎是 0(进程系统 pid)而不是 1。
    • 我刚刚通过一个简单的改进实现了这个“父状态检查”——我只是将原始的 parent_pid 传递给孩子,他们只是检查父 pid 是否保持不变。因此,无论 init/systemd/任何 pid 数值如何,它都应该工作。
    • 如果我们启动一些 3rd 方进程,如 Word、Excel。我们不能对这个子进程进行编码以使其父进程对吗??
    【解决方案3】:

    您可以简单地在子进程中启动从 System.in 读取的线程。如果您不写信给您孩子的标准输入,没有其他人会这样做,并且该线程将“永远”阻塞。但是,如果父进程被杀死或以其他方式死亡,您将获得 EOF(或异常)。所以你也可以关闭孩子。 (子进程以 java.lang.ProcessBuilder 开始)

    【讨论】:

    • 这是一个很好的解决方案。如果阅读是在一个线程中完成的。记得设置线程deamon(true) 否则线程会阻止程序正常关闭。
    • 这个解决方案的开销是多少?
    【解决方案4】:

    提供与@tomkri 的答案类似的黑客方式,并提供演示代码。

    如果您的子进程不需要使用输入流,只需将子进程输入流重定向到其父进程的输入流即可。然后在子进程中添加一个线程以始终读取输入流,当该线程无法从输入流中读取任何内容时,该子进程退出。所以父进程退出->父进程输入流不存在->子进程输入流不存在->子进程退出。

    这里是Java的演示代码。

    父进程:

    package process.parent_child;
    
    import java.io.File;
    import java.io.IOException;
    import java.lang.ProcessBuilder.Redirect;
    
    public class ParentProc {
    
        public static void main(String[] args) {
            System.out.println("I'm parent.");
    
            String javaHome = System.getProperty("java.home");
            String javaBin = javaHome + File.separator + "bin" + File.separator + "java";
            ProcessBuilder builder = new ProcessBuilder(javaBin, "process.parent_child.ChildProc");
    
            // Redirect subprocess's input stream to this parent process's input stream.
            builder.redirectInput(Redirect.INHERIT);
            // This is just for see the output of child process much more easily.
            builder.redirectOutput(Redirect.INHERIT);
            try {
                Process process = builder.start();
                Thread.sleep(5000);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Parent exits.");
        }
    }
    

    子进程:

    package process.parent_child;
    
    import java.io.IOException;
    import java.util.Scanner;
    
    public class ChildProc {
    
    
        private static class StdinListenerThread extends Thread {
    
            public void run() {
                int c;
                try {
                    c = System.in.read();
                    while ( c != -1 ) {
                        System.out.print(c);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                System.out.println("\nChild exits.");
                System.exit(0);
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println("I'm child process.");
            StdinListenerThread thread = new StdinListenerThread();
            thread.start();
            Thread.sleep(10000);
        }
    }
    

    通过以下命令运行此父进程后:

        java process.parent_child.ParentProc
    

    你会看到

        I'm parent.
        I'm child process.
        Parent exits.
        xmpy-mbp:bin zhaoxm$
        Child exits
    

    当父进程退出时,子进程立即退出。

    【讨论】:

    • 不错的解决方案,非常有帮助。
    • 这很好用,当父 java 进程被 Eclipse 用 SIGKILL 终止时,您可以优雅地退出并避免子 java 进程的孤儿(例如)。
    【解决方案5】:

    正如您所发现的,操作系统可以让您绕过这个问题。相反,创建一个由两个进程共享的资源。当父母中止资源时,孩子的反应是关闭。例如:

    在随机高数端口上的父级上以接受模式创建一个带有服务器端 TCP/IP 套接字的线程。当孩子启动时,将端口号作为参数传递(环境变量、数据库条目等)。让它创建一个线程并打开该套接字。让线程永远坐在套接字上。如果连接断开,让孩子退出。

    在父节点上创建一个线程,不断更新文件的更新日期。 (多久取决于你需要多少kill和shutdown之间的粒度。)孩子有一个线程来监控同一个文件的更新时间。如果在特定时间间隔后没有更新,则自动关闭。

    【讨论】:

      【解决方案6】:

      对于单个子进程,您可以通过反转子/父关系来管理它。请参阅 my answer 以了解此问题的后续版本。

      【讨论】:

        【解决方案7】:

        对于 windows,我想出了这个投票技巧:

        static int getPpid(int pid) throws IOException {
            Process p = Runtime.getRuntime().exec("C:\\Windows\\System32\\wbem\\WMIC.exe process where (processid="+pid+") get parentprocessid");
            BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
            br.readLine();
            br.readLine();
            String ppid= br.readLine().replaceAll(" ","");
            return Integer.parseInt(ppid);
        }
        static boolean shouldExit() {
            try {
                String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
                int ppid = getPpid(Integer.parseInt(pid));
                /* pppid */ getPpid(ppid);
            } catch (Exception e) {
                return true;
            } 
            return false;
        }
        

        【讨论】:

          【解决方案8】:

          当父进程停止时,尝试从父进程向子进程发送终止信号

          【讨论】:

          • 杀死一个进程通常不会对其进行任何形式的清理。这正是问题所在。
          • 这取决于你如何杀死一个进程以及在什么操作系统下。 Unix 上的 SIGTERM 允许程序进行清理,而 SIGKILL 则不允许。
          • 你没有回答“如何”; stackoverflow.com/a/272728/2171120 确实(恕我直言,也应该成为公认的答案)。
          【解决方案9】:

          正如我测试的那样,如果父进程被杀死,那么子进程的 ppid 将变为 1。所以我们可能可以杀死任何 ppid = 1 的进程。

          【讨论】:

            【解决方案10】:

            一旦我们有了父进程的进程 ID (PID),我们就可以杀死所有的子进程。我们可以使用windows taskkill命令杀死任何由对应PID启动的进程。

            您可以根据您的用例从以下帖子中找到 PID:https://stackoverflow.com/Questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program

            获得 PID 后,您需要做的就是通过发送以下命令让操作系统为您终止进程:“taskkill /F /T /PID P_id”。在这里将 P_id 替换为您的进程 ID。这里 /T 杀死对应 PID 的进程树中的所有进程。

            您可以使用java.lang.Runtime.exec函数来执行上述命令。

            PS:此解决方案仅适用于 Windows 操作系统。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-08-01
              • 1970-01-01
              • 2015-03-17
              • 2011-07-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-04-13
              相关资源
              最近更新 更多