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

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

在 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杀死子进程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • 使用Gin框架搭建一个Go Web应用程序的方法详解

    使用Gin框架搭建一个Go Web应用程序的方法详解

    在本文中,我们将要实现一个简单的 Web 应用程序,通过 Gin 框架来搭建,主要支持用户注册和登录,用户可以通过注册账户的方式创建自己的账号,并通过登录功能进行身份验证,感兴趣的同学跟着小编一起来看看吧
    2023-08-08
  • Go语言单元测试的实现及用例

    Go语言单元测试的实现及用例

    在日常开发中,我们通常需要针对现有的功能进行单元测试,以验证开发的正确性,本文主要介绍了Go语言单元测试的实现及用例,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • golang的协程上下文的具体使用

    golang的协程上下文的具体使用

    golang的context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等,本文就详细的来介绍一下golang的协程上下文的具体使用,感兴趣的可以了解一下
    2022-04-04
  • linux下通过go语言获得系统进程cpu使用情况的方法

    linux下通过go语言获得系统进程cpu使用情况的方法

    这篇文章主要介绍了linux下通过go语言获得系统进程cpu使用情况的方法,实例分析了Go语言使用linux的系统命令ps来分析cpu使用情况的技巧,需要的朋友可以参考下
    2015-03-03
  • 解决go获取文件md5值不正确的问题

    解决go获取文件md5值不正确的问题

    本文主要介绍了解决go获取文件md5值不正确的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-01-01
  • Go语言中json序列化优化技巧

    Go语言中json序列化优化技巧

    在Go语言开发中,JSON因其简洁和广泛的兼容性,通常被用作数据交换的主要序列化格式,本文将探讨JSON希望对大家有所帮助序列化的一些局限性,也算是一个小坑吧,并给出一些常用的解决方案,
    2024-11-11
  • Golang Mutex互斥锁深入理解

    Golang Mutex互斥锁深入理解

    这篇文章主要为大家介绍了Golang Mutex互斥锁深入理解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Golang性能优化的技巧分享

    Golang性能优化的技巧分享

    性能优化的前提是满足正确可靠、简洁清晰等质量因素,针对 Go语言特性,本文为大家整理了一些Go语言相关的性能优化建议,感兴趣的可以了解一下
    2023-07-07
  • go语言实现一个简单的http客户端抓取远程url的方法

    go语言实现一个简单的http客户端抓取远程url的方法

    这篇文章主要介绍了go语言实现一个简单的http客户端抓取远程url的方法,实例分析了Go语言http操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-03-03
  • Golang如何自定义logrus日志保存为日志文件

    Golang如何自定义logrus日志保存为日志文件

    这篇文章主要给大家介绍了关于Golang如何自定义logrus日志保存为日志文件的相关资料,logrus是目前Github上star数量最多的日志库,logrus功能强大,性能高效,而且具有高度灵活性,提供了自定义插件的功能,很多开源项目都是用了logrus来记录其日志,需要的朋友可以参考下
    2024-02-02

最新评论