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

相关推荐
武子康38 分钟前
大数据-242 离线数仓 - DataX 实战:MySQL 全量/增量导入 HDFS + Hive 分区(离线数仓 ODS
大数据·后端·apache hive
砍材农夫1 小时前
TCP和UDP区别
后端
千寻girling2 小时前
一份不可多得的 《 Django 》 零基础入门教程
后端·python·面试
千寻girling2 小时前
Python 是用来做 AI 人工智能 的 , 不适合开发 Web 网站 | 《Web框架》
人工智能·后端·算法
贾铭2 小时前
如何实现一个网页版的剪映(三)使用fabric.js绘制时间轴
前端·后端
xiaoye20182 小时前
Spring 自定义 Redis 超时:TTL、TTI 与 Pipeline 实战
后端
程序员爱钓鱼5 小时前
GoHTML解析利器:github.com/PuerkitoBio/goquery实战指南
后端·google·go
golang学习记5 小时前
从“大泥球“到模块化单体:Spring Modulith + IntelliJ IDEA 拯救你的代码
后端·intellij idea
颜酱5 小时前
一步步实现字符串计算器:从「转整数」到「带括号与优化」
javascript·后端·算法
Sheffield5 小时前
Alpine是什么,为什么是Docker首选?
linux·docker·容器