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

相关推荐
艾伦~耶格尔21 分钟前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构
叫我龙翔21 分钟前
【Linux】进程间关系与守护进程
linux·运维·服务器·计算机网络
小诸葛的博客23 分钟前
Ubuntu如何如何安装tcpdump
linux·ubuntu·tcpdump
man201725 分钟前
基于spring boot的篮球论坛系统
java·spring boot·后端
__AtYou__33 分钟前
Golang | Leetcode Golang题解之第448题找到所有数组中消失的数字
leetcode·golang·题解
2401_8581205342 分钟前
Spring Boot框架下的大学生就业招聘平台
java·开发语言
S hh42 分钟前
【Linux】进程地址空间
java·linux·运维·服务器·学习
转调1 小时前
每日一练:地下城游戏
开发语言·c++·算法·leetcode
Java探秘者1 小时前
Maven下载、安装与环境配置详解:从零开始搭建高效Java开发环境
java·开发语言·数据库·spring boot·spring cloud·maven·idea
攸攸太上1 小时前
Spring Gateway学习
java·后端·学习·spring·微服务·gateway