Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Golang杀死子进程

golang 设置进程退出时kill所有子进程的三种方法

作者:phiilo

本文主要介绍了golang 设置进程退出时kill所有子进程的三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在 Go 语言中使用 os/exec.Command 启动子进程时,默认情况下子进程不会随父进程(当前进程)的退出而自动终止,因为子进程会被系统(如 init 或系统进程管理器)收养并继续运行。要实现“父进程退出时自动终止子进程”的功能,需要根据操作系统采用不同的策略。没有完美的跨平台标准解决方案,但以下是常见实现方式,包括手动处理信号和平台特定机制。以下解释逐步如何实现,并提供代码示例。

1.通用方式:捕获信号并手动终止子进程(跨平台,适用于正常退出和可捕获信号)

这是一种简单、跨平台的做法:父进程监听退出信号(如 SIGINT、SIGTERM),在收到信号时主动杀死子进程。这种方式适用于父进程正常退出或被可捕获信号终止的情况,但如果父进程被 kill -9 (SIGKILL) 强制杀死,则无效(因为 SIGKILL 无法捕获)。

步骤

代码示例

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 杀死。

注意

代码示例

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 的最后一个句柄关闭(父进程退出)时,自动杀死所有关联进程。

注意

代码示例(简化版,需要处理错误和清理):

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 关闭,子进程被终止。

其他注意

到此这篇关于golang 设置进程退出时kill所有子进程的三种方法的文章就介绍到这了,更多相关Golang杀死子进程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

您可能感兴趣的文章:
阅读全文