golang 设置进程退出时kill所有子进程的三种方法
作者:phiilo
在 Go 语言中使用 os/exec.Command 启动子进程时,默认情况下子进程不会随父进程(当前进程)的退出而自动终止,因为子进程会被系统(如 init 或系统进程管理器)收养并继续运行。要实现“父进程退出时自动终止子进程”的功能,需要根据操作系统采用不同的策略。没有完美的跨平台标准解决方案,但以下是常见实现方式,包括手动处理信号和平台特定机制。以下解释逐步如何实现,并提供代码示例。
1.通用方式:捕获信号并手动终止子进程(跨平台,适用于正常退出和可捕获信号)
这是一种简单、跨平台的做法:父进程监听退出信号(如 SIGINT、SIGTERM),在收到信号时主动杀死子进程。这种方式适用于父进程正常退出或被可捕获信号终止的情况,但如果父进程被 kill -9 (SIGKILL) 强制杀死,则无效(因为 SIGKILL 无法捕获)。
步骤:
- 使用
signal.Notify监听信号。 - 启动子进程后,记录
PID。 - 在信号处理中逐个终止子进程(如果需要杀死子进程的子进程,可使用进程组,详见下文)。
代码示例:
package main
import (
"context"
"fmt"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
)
func main() {
// 启动子进程,例如运行 "sleep 60"
cmd := exec.Command("sleep", "60")
if err := cmd.Start(); err != nil {
fmt.Println("Start error:", err)
return
}
fmt.Printf("Child process started with PID: %d\n", cmd.Process.Pid)
// 监听信号
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// goroutine 处理信号
go func() {
sig := <-sigs
fmt.Printf("Received signal: %v\n", sig)
if cmd.Process != nil {
if err := cmd.Process.Kill(); err != nil {
fmt.Println("Kill error:", err)
}
}
os.Exit(0)
}()
// 等待子进程正常结束(可选,如果子进程有自己的退出逻辑)
if err := cmd.Wait(); err != nil {
fmt.Println("Wait error:", err)
}
}
扩展:使用进程组杀死子进程及其后代(Unix-like 系统):
要杀死整个进程树:
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
// 在杀死时:
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
2.Linux 特定:使用 Pdeathsig 自动发送信号
在 Linux 上,可以使用 syscall.SysProcAttr.Pdeathsig 设置当父进程(或创建线程)死亡时,内核自动向子进程发送指定信号(如 SIGTERM 或 SIGKILL)。这实现了“自动”终止,即使父进程被 SIGKILL 杀死。
注意:
- 只适用于 Linux(基于 prctl(PR_SET_PDEATHSIG))。
- 如果子进程是多线程的,可能有边缘问题(见 Go issue #27505)。
- 子进程需要能响应信号(例如,如果它忽略 SIGTERM,则用 SIGKILL)。
代码示例:
package main
import (
"fmt"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sleep", "60")
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL, // 或 syscall.SIGTERM
}
if err := cmd.Start(); err != nil {
fmt.Println("Start error:", err)
return
}
fmt.Printf("Child process started with PID: %d\n", cmd.Process.Pid)
// 父进程可以继续其他工作,或直接退出测试
// cmd.Wait() // 可选
}
3.Windows 特定:使用 Job Object 自动终止
在 Windows 上,没有直接等价于 Pdeathsig 的机制,但可以使用 Windows Job Object 将进程分组,并设置 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 标志:当 Job Object 的最后一个句柄关闭(父进程退出)时,自动杀死所有关联进程。
注意:
- Go 标准库不直接支持,需要使用
golang.org/x/sys/windows包调用 Windows API。 - 需要安装依赖:
go get golang.org/x/sys/windows。 - 这会终止子进程及其后代。
代码示例(简化版,需要处理错误和清理):
package main
import (
"fmt"
"os/exec"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
func main() {
// 创建 Job Object
job, err := windows.CreateJobObject(nil, nil)
if err != nil {
fmt.Println("CreateJobObject error:", err)
return
}
defer windows.CloseHandle(job)
// 设置 Kill on Job Close
var info windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION
info.BasicLimitInformation.LimitFlags = windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
err = windows.SetInformationJobObject(
job,
windows.JobObjectExtendedLimitInformation,
uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info)),
)
if err != nil {
fmt.Println("SetInformationJobObject error:", err)
return
}
// 将当前进程分配到 Job Object(子进程会继承)
err = windows.AssignProcessToJobObject(job, windows.CurrentProcess())
if err != nil {
fmt.Println("AssignProcessToJobObject error:", err)
return
}
// 启动子进程
cmd := exec.Command("timeout", "/t", "60") // Windows 等价于 sleep
if err := cmd.Start(); err != nil {
fmt.Println("Start error:", err)
return
}
fmt.Printf("Child process started with PID: %d\n", cmd.Process.Pid)
// cmd.Wait() // 可选
}
说明:子进程启动后会自动关联 Job Object(因为父进程已关联)。父进程退出时,Job Object 关闭,子进程被终止。
其他注意
- 跨平台实现:可以使用条件编译(
//go:build linux等)来区分平台特定代码。对于通用场景,优先使用信号捕获方式。 - 测试:在 Unix 上用
kill <pid>测试;在 Windows 上用 Task Manager 杀死父进程。 - 局限性:没有方式能 100% 保证在所有情况下(如父进程崩溃)自动终止,尤其是跨平台。如果子进程是 daemon 或忽略信号,可能需要额外处理。
到此这篇关于golang 设置进程退出时kill所有子进程的三种方法的文章就介绍到这了,更多相关Golang杀死子进程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
