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

相关推荐
geovindu1 分钟前
java: Facade Pattern
java·开发语言·后端·外观模式·结构型模式
listening7772 分钟前
HarmonyOS 6.1 元服务深度优化:从“秒开”到“常驻”的极致体验
java·开发语言·spring
布朗克1684 分钟前
Go 入门到精通-29-测试与基准
开发语言·后端·golang·测试与基准
你驴我18 分钟前
WhatsApp Cloud API 速率限制下的令牌桶与退避策略实践
网络·后端·python
ClouGence39 分钟前
SAP HANA 到 Doris 数据迁移:4 种方案对比与迁移教程
数据库·后端·dba
霸道流氓气质40 分钟前
ApiPost 中配置自动获取 Token 并调用业务接口完整指南
java·服务器·数据库
LVZ42 分钟前
用了半年 AI 写代码,最烦的不是代码,是环境
前端·后端·开源
格林威1 小时前
工业相机Chunk功能全解析:图像嵌入时间戳、编码器元数据(附堡盟C#代码)
开发语言·人工智能·数码相机·计算机视觉·c#·视觉检测·工业相机
云边有个稻草人1 小时前
从一条危险的 WHERE 条件说起:金仓数据库逻辑安全警示
后端
用户7713970207061 小时前
从一脸懵到熟练使用:我的C#CAD二次开发日常中那些“反人类“的循环技巧
后端