Golang:子进程变成僵尸进程

10

我有一个使用Go编写的应用程序,它重新定向二进制文件的STDIN和STDOUT,然后运行它们。简而言之,我正在执行以下操作:

- 使用二进制路径创建命令对象(我们将对象称为命令A) - 使用二进制路径创建命令对象(我们将其称为命令B) - 将命令B的标准输出设置为命令A的标准输入 - 启动命令A - 启动命令B

我注意到,每当命令B的进程在命令A正在运行时退出时,它会成为进程表中的僵尸进程。

以下是一个示例:

commandA := exec.Command("samplebin")
commandB := exec.Command("sample2bin")

cmdAStdin := commandA.StdinPipe()

commandB.Stdout = cmdAStdin

commandA.Start()
commandB.Start()

如果commandA仍在运行时commandB退出,为什么commandB会成为僵尸进程?我在Ubuntu 14上运行Go 1.5。


1
@chris-dodd 说得对,如果你只是不想要僵尸进程,你可以通过添加以下代码来忽略 SIGCHLD 信号: signal.Ignore(syscall.SIGCHLD) - AnonymousX
2个回答

22

当进程退出时,不管其他进程是否在运行,它始终会成为僵尸进程。这就是进程终止的方式。该进程将保持为僵尸状态,直到其父进程调用wait获取其退出状态或通过忽略SIGCHLD(可能在子进程退出之前)表明对子进程不感兴趣。 在发生这种情况之前,它将一直保持为僵尸状态,以防退出状态丢失。

在您的示例中,似乎您的进程(创建进程的进程)是父进程,因此A和B将一直保持为僵尸进程,直到您的进程收集它们。

如果进程在仍有子进程(正在运行或已变成僵尸进程)时退出,那么这些子进程将被重新分配给退出进程的父进程,后者通常会忽略退出状态(清除僵尸进程)。


那么,SIGCHLD是由子进程在它们变成僵尸进程之前发送的信号?那么如何“忽略”SIGCHLD呢?通过捕获信号并什么也不做吗? - Anfernee
当子进程变成僵尸进程时,内核会向父进程发送SIGCHLD信号。如果你想忽略SIGCHLD信号,但仍然希望得到僵尸进程,可以将SIGCHLD操作设置为 SIG_DFL(默认值),而不是SIG_IGN。默认操作是什么都不做,但仍然会得到僵尸进程。 - Chris Dodd
1
我不想要僵尸进程,我想清理掉已经退出的进程。我尝试在主应用程序中设置信号以忽略SIGCHLD,但仍然会出现僵尸进程,所以最终我调用了Wait()函数。 - Anfernee
如果您在 Docker 容器的 pid:1 中运行进程(即使最终调用了 wait),也会导致僵尸进程。在这种情况下,https://github.com/krallin/tini/ 会很有帮助。 - mckelvin
@Anfernee,我也不太确定Chris在说什么,但忽略SIGCHLD信号意味着你不会收到该信号,而非子进程不变成僵尸进程,正如你所发现的那样。 - Alexis Wilke

3

同意第一个答案,退出的进程会变成僵尸进程,直到被另一个进程等待。以下是我在go中处理事情的方式。

package main

import (
    "bytes"
    "io"
    "os"
    "os/exec"
)

func main() {
    c1 := exec.Command("samplebin")
    c2 := exec.Command("sample2bin")

    r, w := io.Pipe()
    c1.Stdout = w
    c2.Stdin = r

    var b2 bytes.Buffer
    c2.Stdout = &b2

    // Writing without a reader will deadlock so write in a goroutine
    go func() {
        // Close the writer or the pipe will not be closed for c2
        defer w.Close()
        defer c1.Wait()
        c1.Start()
    }()
    defer c2.Wait()
    c2.Start()
    io.Copy(os.Stdout, &b2)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接