Linux|如何避免 Go 命令行执行产生“孤儿”进程?( 二 )


那么我们给要执行的进程 , 新建一个进程组 , 在 Kill 不就可以了嘛 。 在 linux 当中 , 通过 setpgid 方法来设置进程组 ID , 定义如下:

如果将 pid 和 pgid 同时设置成 0 , 也就是 setpgid(00) , 则会使用当前进程为进程组 leader 并创建新的进程组 。
那么在 Go 程序中 , 可以通过 cmd.SysProcAttr 来设置创建新的进程组 , 修改后的代码如下:
func kill(cmd *exec.Cmd) func() { return func() { if cmd != nil { // cmd.Process.Kill() syscall.Kill(-cmd.Process.Pid syscall.SIGKILL)func main() { cmd := exec.Command(\"/bin/bash\" \"-c\" \"watch toptop.log\") cmd.SysProcAttr =syscall.SysProcAttr{ Setpgid: truetime.AfterFunc(1*time.Second kill(cmd)) err := cmd.Run() fmt.Printf(\"pid=%d err=%s\\" cmd.Process.Pid err) 再次执行:
go run main.gopid=29397 err=signal: killed 再次查看进程:
ps -jUSER PID PPID PGID SESS JOBC STAT TT TIME COMMAND 发现 watch 的进程都不存在了 , 那我们在看看是否还会有孤儿进程:
# 由于我测试的环境是mac , 因此这个脚本只能在mac执行ps -j | head -1;ps -j | awk '{if ($3 ==1$1 !=\"root\"){print $0' | headUSER PID PPID PGID SESS JOBC STAT TT TIME COMMAND 已经没有孤儿进程了 , 问题至此已经完全解决 。
三 子进程监听父进程是否退出(只能在 linux 下执行) 假设要调用的程序也是我们自己写的其他应用程序 , 那么可以使用 Linux 的 prctl 方法来处理 ,prctl 方法的定义如下:

这个方法有一个重要的 option:PR_SET_PDEATHSIG , 通过这个来接收父进程的退出 。
让我们来再次构造一个有问题的程序 。
有两个文件 , 分别为 main.go 和 child.go 文件 , main.go 会调用 child.go 文件 。
main.go 文件:
package mainimport ( \"os/exec\")func main() { cmd := exec.Command(\"./child\") cmd.Run() child.go 文件:
package mainimport ( \"fmt\" \"time\")func main() { for { time.Sleep(200 * time.Millisecond) fmt.Println(time.Now())在 Linux 环境中分别编译这两个文件:
// 编译 main.go 生成 main 二进制文件go build -o main main.go// 编译 child.go 生成 child 二进制文件go build -o child child.go 执行 main 二进制文件:
./main 查看他们的进程:
ps -efUID PID PPID C STIME TTY TIME CMDroot 1 0 0 06:05 pts/0 00:00:00 /bin/bashroot 11514 1 0 12:12 pts/0 00:00:00 ./mainroot 11520 11514 0 12:12 pts/0 00:00:00 ./child 可以看到 main 和 child 的进程 , child 是 main 的子进程 , 我们将 main 进程 kill 掉 , 在查看进程状态:
kill -9 11514ps -efUID PID PPID C STIME TTY TIME CMDroot 1 0 0 06:05 pts/0 00:00:00 /bin/bashroot 11520 1 0 12:12 pts/0 00:00:00 ./child 我们可以看到 child 的进程 , 他的 PPID 已经变成了 1 , 说明这个进程已经变成了孤儿进程 。
那接下来我们可以使用 PR_SET_PDEATHSIG 来保证父进程退出 , 子进程也退出 , 大致方式有两种:使用 CGO 调用和使用 syscall.RawSyscall 来调用 。
1 使用 CGO
将 child 修改成如下内容:

程序中 , 使用 CGO , 为了简单的展示 , 在 Go 文件中编写了 C 的 killTest 方法 , 并调用了 prctl 方法 , 然后在 Go 程序中调用 killTest 方法 , 让我们重新编译执行一下 , 再看看进程:
go build -o child child.go./mainps -ef UID PID PPID C STIME TTY TIME CMDroot 1 0 0 06:05 pts/0 00:00:00 /bin/bashroot 11663 1 0 12:28 pts/0 00:00:00 ./mainroot 11669 11663 0 12:28 pts/0 00:00:00 ./child 再次 kill 掉 main , 并查看进程:
kill -9 11663ps -efUID PID PPID C STIME TTY TIME CMDroot 1 0 0 06:05 pts/0 00:00:00 /bin/bash 可以看到 child 的进程也已经退出了 , 说明 CGO 调用的 prctl 生效了 。
2 syscall.RawSyscall 方法
也可以采用 Go 中提供的 syscall.RawSyscall 方法来替代调用 CGO , 在 Go 的文档中 , 可以查看到 syscall 包中定义的常量(查看 linux , 如果是本地 godoc , 需要指定 GOOS=linux) , 可以看到我们要用的几个常量以及他们对应的数值: