go 程序被意外kill后出现僵尸进程解决方案

go 管理自身子进程(防止僵尸进程出现)

写这篇文章是因为最近有同事竟然会知道异步启动子进程,不会关闭,最后导致导致僵尸进程出现,而且由于子进程会随着业务的使用越开越多,主进程一旦被kill掉就会不得不手动一个一个kill。

大概情况就是这样的(仅做问题浮现)

go 复制代码
package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
)

func child() {
	li, err := net.Listen("tcp", "127.0.0.1:1999")
	if err != nil {
		log.Fatalln(err)
	}
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	<-ch
	li.Close()
}
func main() {
	fmt.Println(os.Getpid())
	ischild := flag.Bool("child", false, "child")
	flag.Parse()
	if *ischild {
		child()
		return
	}
	cmd := exec.Command("./demo", "--child")//随着业务进展,这个是长期运行的,会起很多个
	cmd.Start()
	cmd.Wait()
}

命令行启动后再被kill掉过后,监听1999端口的进程就停不下来了,由于业务其实很多个这样的子进程成了僵尸进程。我当时第一反应不就是以前c fork一个子进程来当守护进程然后主程序退出的操作。

其实有种不讲武德的操作可以管理这种僵尸进程,当我拿出cgo助攻一小段,阁下又该如何应对

go 复制代码
package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
	"time"
	"unsafe"
)

//#include <unistd.h>
import "C"

func Fork() int32 {
	return int32(C.fork())
}
func child() {
	li, err := net.Listen("tcp", "127.0.0.1:1999")
	if err != nil {
		log.Fatalln(err)
	}
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	<-ch
	li.Close()
}
//简简单单实现一个子进程管理器,走unix socket 流。你可以用其它ipc方式
func process_manage() {
	lis, err := net.ListenUnix("unix", &net.UnixAddr{Name: "man.sock"})
	if err != nil {
		log.Fatalln("listen man.sock failed " + err.Error())
	}
	var (
		size int
		buff []byte = make([]byte, unsafe.Sizeof(size))

		con net.Conn
		pid *int = (*int)(unsafe.Pointer(&buff[0]))
	)
	var pidlist []int
	for err == nil {
		con, err = lis.Accept()
		for err == nil {
			_, err = con.Read(buff)
			if err == nil {
				if *pid != 0 {
					pidlist = append(pidlist, *pid)
				}
			}
		}
	}
	lis.Close()
	for _, cpid := range pidlist {
		err = syscall.Kill(cpid, syscall.SIGINT)
		if err != nil {
			fmt.Fprintln(os.Stderr, "send to pid", cpid, "failed", err)
		}
	}
}
func main() {
	ischild := flag.Bool("child", false, "child")
	flag.Parse()
	if *ischild {
		child()
		return
	}
	switch Fork() {
	case 0:
		process_manage()
		return
	case -1:
		log.Fatalln("crate child process failed")
		return
	default:
		fmt.Println(os.Getpid())
		time.Sleep(time.Millisecond * 300)
		con, err := net.Dial("unix", "man.sock")
		if err != nil {
			log.Fatalln("dial man.sock failed " + err.Error())
		}
		var size int
		var buff []byte = make([]byte, unsafe.Sizeof(size))
		cmd := exec.Command("./demo", "--child")
		cmd.Start()
		var pidptr *int = (*int)(unsafe.Pointer(&buff[0]))
		*pidptr = cmd.Process.Pid
		_, err = con.Write(buff)
		if err != nil {
			fmt.Fprintln(os.Stderr, "write to daemon failed", err)
		}
		cmd.Wait()
		return
	}
}

程序每异步开启一个子进程命令就把pid传送给我们的守护进程,若主进程被kill了,主进程和守护进程之间连接就会断,守护进程将给所有开启的子进程发送SIGINT信号,推荐SIGINT,SIGTERM。这两个可以捕获,大家也都知道这两个信号。这里我图方便和守护进程之间通信直接用的unix socket流,你也可以用其它ipc

相关推荐
你的人类朋友6 小时前
说说签名与验签
后端
databook6 小时前
Manim实现脉冲闪烁特效
后端·python·动效
canonical_entropy9 小时前
AI时代,我们还需要低代码吗?—— 一场关于模型、演化与软件未来的深度问答
后端·低代码·aigc
颜如玉10 小时前
HikariCP:Dead code elimination优化
后端·性能优化·源码
考虑考虑10 小时前
Jpa使用union all
java·spring boot·后端
bobz96511 小时前
virtio vs vfio
后端
Rexi12 小时前
“Controller→Service→DAO”三层架构
后端
bobz96512 小时前
计算虚拟化的设计
后端
深圳蔓延科技12 小时前
Kafka的高性能之路
后端·kafka
Barcke12 小时前
深入浅出 Spring WebFlux:从核心原理到深度实战
后端