go 管理自身子进程(防止僵尸进程出现)
写这篇文章是因为最近有同事竟然会知道异步启动子进程,不会关闭,最后导致导致僵尸进程出现,而且由于子进程会随着业务的使用越开越多,主进程一旦被kill掉就会不得不手动一个一个kill。
大概情况就是这样的(仅做问题浮现)
package mainimport ("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)<-chli.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助攻一小段,阁下又该如何应对
package mainimport ("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)<-chli.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 intbuff []byte = make([]byte, unsafe.Sizeof(size))con net.Connpid *int = (*int)(unsafe.Pointer(&buff[0])))var pidlist []intfor 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()returncase -1:log.Fatalln("crate child process failed")returndefault: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 intvar 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