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