Go程序当父进程被kill,子进程也自动退出的问题记录

平常我们启动一个后台进程,会通过nouhp &的方式启动,这样可以在退出终端会话的时候,进程仍然可以继续在后台执行(进程的父进程id会从原来的bash进程变成1)
在go程序中,通过nouhp &的方式启动子进程,预期是即使父进程挂掉,子进程也能继续执行
但是测试过程中发现,当父进程被kill,子进程也会自动退出

首先需要了解下什么是SIGHUP和SIGTERM

1.SIGHUP(Hangup)信号通常是由终端或控制台断开时产生的信号
    它的作用是通知进程重新读取其配置文件,或者让进程重新初始化,以便于适应新的环境。
    在进程收到该信号时,一般会在日志中记录相关信息,然后进行优雅的退出或重新初始化。
2.SIGTERM(Terminate)信号是进程终止信号
    它通常是由kill命令发送给进程的。它的作用是请求进程正常地退出,
    进程在接收到该信号后,可以在清理后退出。
    如果进程没有处理SIGTERM信号,则可以使用kill -9命令强制杀死进程。

正常情况下,一个程序如果没有进行特别处理,那么收到SIGHUP、SIGTERM信号都会退出

通常我们在一个终端会话中启动一个进程,如果只是通过&后台启动,那么当会话关闭的时候,进程也会自动退出
这是因为会话关闭的时候,会向子进程发送SIGHUP信号,导致子进程也跟着退出
而nohup的作用就是忽略NOHUP信号,避免进程退出

go程序中可以用signal.Notify监听SIGHUP信号修改默认行为,示例代码:

package main
 
import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)
 
func main() {
    // 创建一个channel用于接收信号
    signals := make(chan os.Signal, 1)
 
    // 注册信号
    signal.Notify(signals, syscall.SIGTERM, syscall.SIGHUP)
 
    // 在goroutine中等待信号
    go func() {
        for {
            select {
            case sig := <-signals:
                switch sig {
                case syscall.SIGTERM:
                    fmt.Println("Received SIGTERM, shutting down gracefully...")
                    // 做一些清理工作
                    os.Exit(0)
                case syscall.SIGHUP:
                    fmt.Println("Received SIGHUP, reloading configuration...")
                    // 重新加载配置
                }
            }
        }
    }()
 
    // 主进程继续执行其他任务
    fmt.Println("Server started...")
    select {}
}

通过在子进程中用signal.Notify监听SIGHUP、SIGTERM信号,并打印日志,来进行测试(kill -1发送SIGHUP信号,kill发送SIGTERM信号)
查看日志发现,父进程被kill,子进程会收到SIGTERM信号
而nohup只是忽略SIGHUP信号,所以使用nohup启动自然就不能防止子进程退出了

解决方案是启动子进程时,修改子进程进程组id,这样子进程就不会收到SIGTERM信号了
Go示例代码:

package main
 
import (
    "fmt"
    "os/exec"
    "strings"
    "syscall"
)
 
func main() {
    //这里child是上面子进程编译成的二进制程序
    cmd := exec.Command("/bin/bash", "-c", "./child")
    //SysProcAttr 字段被设置为 Setpgid 为 true,这将使子进程的进程组 ID 与其父进程不同。Pdeathsig 被设置为空信号,这意味着子进程在父进程退出时不会收到任何信号
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Setpgid:   true,
        Pdeathsig: syscall.Signal(0),
    }
    output, err := cmd.CombinedOutput()
    rs := strings.TrimSpace(string(output))
    if err != nil {
        fmt.Println("Command execution failed:", err, "rs:", rs)
        os.Exit(1)
    }
    fmt.Println("rs:", rs)
}

ps -eo pid,ppid,pgrp,session,comm
可以通过这个命令来查看进程进程组id

还有一种情况要注意,即使不用Setpgid,使用kill -9的方式杀父进程,子进程也是不会退出的
针对一些希望父进程结束的时候,子进程也被跟着退出的场景,要么谨慎使用kill -9,要么自己做好进程退出的机制处理

参考资料:
https://blog.csdn.net/qq_34021712/article/details/115587702
https://cloud.tencent.com/developer/article/1497217
https://www.jianshu.com/p/e147d856074c%20

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
可以使用Linux系统提供的kill()函数来实现父进程子进程发送kill信号。该函数的原型如下: ``` #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); ``` 其中,pid参数为要发送信号的进程ID,sig参数为要发送的信号类型。 在父进程中,可以先使用fork()函数创建子进程,然后使用kill()函数向子进程发送信号。示例代码如下: ```c #include <stdio.h> #include <unistd.h> #include <signal.h> int main() { pid_t pid; int status; pid = fork(); if (pid < 0) { printf("Error: fork() failed\n"); return 1; } else if (pid == 0) { // 子进程执行的代码 printf("Child process is running\n"); sleep(10); printf("Child process is exiting\n"); } else { // 父进程执行的代码 printf("Parent process is running, child pid = %d\n", pid); sleep(5); printf("Parent process is sending SIGKILL to child process\n"); kill(pid, SIGKILL); wait(&status); printf("Child process exited with status: %d\n", status); } return 0; } ``` 在上面的示例代码中,父进程首先使用fork()函数创建子进程,然后在子进程中打印一些信息并休眠10秒钟,最后退出。在父进程中,打印一些信息后休眠5秒钟,然后使用kill()函数向子进程发送SIGKILL信号,强制结束子进程。父进程使用wait()函数等待子进程退出,并打印子进程退出状态。 执行上面的代码,将会输出如下结果: ``` Parent process is running, child pid = 1234 Child process is running Parent process is sending SIGKILL to child process Child process exited with status: 9 ``` 可以看到,父进程成功地向子进程发送了SIGKILL信号,强制结束了子进程

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值