go 管理自身子进程(防止僵尸进程出现)
写这篇文章是因为最近有同事竟然会知道异步启动子进程,不会关闭,最后导致导致僵尸进程出现,而且由于子进程会随着业务的使用越开越多,主进程一旦被kill掉就会不得不手动一个一个kill。
大概情况就是这样的(仅做问题浮现)
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助攻一小段,阁下又该如何应对
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