golang

场景一

我们来看看下面两个脚本会产生什么问题:

创建两个 shell 脚本

  • start.sh
  • sub.sh

执行脚本

输出结果

$ ./start.sh 
0
1
2
...

进程关系

查看进程信息

sub.shstart.shsub.shstart.shPGID
start.sh
start.shsub.shsub.shPID

问题1:

sub.sh

场景二

sub.shstart.sh
golanggolang
golangos/execstart.sh

执行程序

查看进程

gostart.shsub.sh
main.gostart.shsub.sh
start.sh
start.sh

再查看进程

start.shPPIDstart.shPPIDlog.Println(cmd.Process.Pid)

问题2:

PPIDgolang

问题分析

PPID孤儿进程cmdgostart.sh僵尸进程
孤儿进程僵尸进程

孤儿进程

UNIX
initsystemd收养init孤儿进程

解决&预防

  • 终止机制:强制杀死孤儿进程(最常用的手段);

  • 再生机制:服务器在指定时间内查找调用的客户端,若找不到则直接杀死孤儿进程;

  • 超时机制:给每个进程指定一个确定的运行时间,若超时仍未完成则强制终止之。若有需要,亦可让进程在指定时间耗尽之前申请延时。

  • 进程组:因为父进程终止或崩溃都会导致对应子进程成为孤儿进程,所以也无法预料一个子进程执行期间是否会被“遗弃”。有鉴于此,多数类UNIX系统都引入了进程组以防止产生孤儿进程。

僵尸进程

UNIXwaitkill

解决&预防

killinitinitwait

查看进程详情

  • USER:进程的所属用户
  • PID:进程的进程ID号
  • RSS:进程占用的固定的内存量 (Kbytes)
  • S:查看进程状态
  • CMD:进程对应的实际程序

进程状态(S)

  • R:运行 Runnable (on run queue) 正在运行或在运行队列中等待
  • S:睡眠 Sleeping 休眠中,受阻,在等待某个条件的形成或接受到信号
  • I:空闲 Idle
  • Z:僵死 Zombie(a defunct process) 进程已终止,但进程描述符存在, 直到父进程调用wait4()系统调用后释放
  • D:不可中断 Uninterruptible sleep (ususally IO) 收到信号不唤醒和不可运行, 进程必须等待直到有中断发生
  • T:终止 Terminate 进程收到SIGSTOP、SIGSTP、 SIGTIN、SIGTOU信号后停止运行运行
  • P:等待交换页
  • W:无驻留页 has no resident pages 没有足够的记忆体分页可分配
  • X:死掉的进程

Go解决方案

kill -- -PID
cmd.Wait()
os/exec_unix
syscall.Wait4

总结

严格地来说,僵尸进程并不是问题的根源,罪魁祸首是产生出大量僵尸进程的那个父进程。

因此,当我们寻求如何消灭系统中大量的僵尸进程时,更应该是在实际的开发过程中,思考如何避免僵尸进程的产生。

参考:

https://pkg.go.dev/syscall

https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/syscall/syscall_linux.go;l=279

https://pkg.go.dev/os/exec