一文搞懂Go Exec 僵尸与孤儿进程
作者:WilburXu 发布时间:2023-10-21 07:14:07
最近,使用 golang
去管理本地应用的生命周期,期间有几个有趣的点,今天就一起看下。
场景一
我们来看看下面两个脚本会产生什么问题:
创建两个 shell 脚本
start.sh
#!/bin/sh
sh sub.sh
sub.sh
#!/bin/sh
n=0
while [ $n -le 100 ]
do
echo $n
let n++
sleep 1
done
执行脚本
输出结果
$ ./start.sh
0
1
2
...
进程关系
查看进程信息
ps -j
USER PID PPID PGID SESS JOBC STAT TT TIME COMMAND
root 31758 31346 31758 0 1 S+ s000 0:00.00 /bin/sh ./start.sh
root 31759 31758 31758 0 1 S+ s000 0:00.01 sh sub.sh
sub.sh
的 父进程(PPID)为start.sh
的进程id(PID)sub.sh
和start.sh
两个进程的PGID
是同一个,( 属一个进程组)。
删除 start.sh
的进程
kill -9 31758
# 再查看进程组
ps -j
## 返回
USER PID PPID PGID SESS JOBC STAT TT TIME COMMAND
root 31759 1 31758 0 0 S s000 0:00.03 sh sub.sh
start.sh
进程不在了sub.sh
进程还在执行sub.sh
进程的PID
变成了 1
问题1:
那sub.sh
这个进程现在属于什么?
场景二
假设sub.sh
是实际的应用, start.sh
是应用的启动脚本。
那么,golang
是如何管理他们的呢? 我们继续看看下面 关于golang
的场景。
在上面两个脚本的基础上,我们用golang
的 os/exec
库去调用 start.sh
脚本
package main
import (
"context"
"log"
"os"
"os/exec"
"time"
)
func main() {
cmd := exec.CommandContext(context.Background(), "./start.sh")
// 将 start.sh 和 sub.sh 移到当前目录下
cmd.Dir = "/Go/src/go-code/cmd/"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
log.Printf("cmd.Start error %+v \n", err)
}
for {
select {
default:
log.Println(cmd.Process.Pid)
time.Sleep(2 * time.Second)
}
}
}
执行程序
go run ./main.go
查看进程
ps -j
USER PID PPID PGID SESS JOBC STAT TT TIME COMMAND
root 45458 45457 45457 0 0 Ss+ s004 0:00.03 ...___1go_build_go_code_cmd
root 45462 45458 45457 0 0 S+ s004 0:00.01 /bin/sh ./start.sh
root 45463 45462 45457 0 0 S+ s004 0:00.03 sh sub.sh
发现 go
、 start.sh
、sub.sh
三个进程为同一个进程组(同一个 PGID)
父子关系为: main.go
-> start.sh
-> sub.sh
删除 start.sh
的进程
实际场景,有可能启动程序挂了,导致我们无法监听到执行程序的情况,删除start.sh
进程,模拟下场景 :
kill -9 45462
再查看进程
ps -j
USER PID PPID PGID SESS JOBC STAT TT TIME COMMAND
root 45458 45457 45457 0 0 Ss+ s004 0:00.03 ...___1go_build_go_code_cmd
root 45462 1 45457 0 0 S+ s004 0:00.01 (bash)
root 45463 45462 45457 0 0 S+ s004 0:00.03 sh sub.sh
发现没,
start.sh
的PPID
为1即使
start.sh
的PPID
变成了1 ,log.Println(cmd.Process.Pid)
还持续的输出 .
问题2:
那如果 PPID
为1 ,golang
程序不就无法管理了吗? 即使 sub.sh 退出也不知道了,那要如何处理?
问题分析
两个场景中, 都有一个共同的点,就是
PPID
为1,这妥妥的成为没人要的娃了——孤儿进程
场景二中,如果
cmd
的没有进程没有被回收,go
程序也无法管理,那么start.sh
就成为了占着茅坑不拉屎的子进程——僵尸进程
那究竟什么是孤儿进程
和 僵尸进程
?
孤儿进程
在类 UNIX
操作系统中,孤儿进程(Orphan Process)指:是在其父进程执行完成或被终止后仍继续运行的一类进程。
为避免孤儿进程退出时无法释放所占用的资源而僵死,任何孤儿进程产生时都会立即为系统进程 init
或 systemd
自动接收为子进程,这一过程也被称为收养
。在此需注意,虽然事实上该进程已有init
作为其父进程,但由于创建该进程的进程已不存在,所以仍应称之为孤儿进程
。孤儿进程会浪费服务器的资源,甚至有耗尽资源的潜在危险。
解决&预防
终止机制:强制杀死孤儿进程(最常用的手段);
再生机制:服务器在指定时间内查找调用的客户端,若找不到则直接杀死孤儿进程;
超时机制:给每个进程指定一个确定的运行时间,若超时仍未完成则强制终止之。若有需要,亦可让进程在指定时间耗尽之前申请延时。
进程组:因为父进程终止或崩溃都会导致对应子进程成为孤儿进程,所以也无法预料一个子进程执行期间是否会被“遗弃”。有鉴于此,多数类UNIX系统都引入了进程组以防止产生孤儿进程。
僵尸进程
在类 UNIX
操作系统中,僵尸进程(zombie process)指:完成执行(通过exit系统调用,或运行时发生致命错误或收到终止信号所致),但在操作系统的进程表中仍然存在其进程控制块,处于"终止状态"的进程。
正常情况下,进程直接被其父进程 wait
并由系统回收。而僵尸进程与正常进程不同,kill
命令对僵尸进程无效,并且无法回收,从而导致资源泄漏。
解决&预防
收割僵尸进程的方法是通过 kill
命令手工向其父进程发送SIGCHLD信号。如果其父进程仍然拒绝收割僵尸进程,则终止父进程,使得 init
进程收养僵尸进程。init
进程周期执行 wait
系统调用收割其收养的所有僵尸进程。
查看进程详情
# 列出进程
ps -l
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 process group,而不是只 kill 父进程,在 Linux 里面使用的是 kill -- -PID
) 与 进程wait方案,结果如下:
package main
import (
"context"
"log"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
ctx := context.Background()
cmd := exec.CommandContext(ctx, "./start.sh")
// 设置进程组
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
cmd.Dir = "/Users/Wilbur/Project/Go/src/go-code/cmd/"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
log.Printf("cmd.Start error %+v \n", err)
}
// 监听进程wait
errCmdCh := make(chan error, 1)
go func() {
errCmdCh <- cmd.Wait()
}()
for {
select {
case <-ctx.Done():
log.Println("ctx.done")
pid := cmd.Process.Pid
if err := syscall.Kill(-1*pid, syscall.SIGKILL); err != nil {
return
}
case err := <-errCmdCh:
log.Printf("errCmdCh error %+v \n", err)
return
default:
log.Println(cmd.Process.Pid)
time.Sleep(2 * time.Second)
}
}
}
剖析 cmd.Wait()
源码
在 os/exec_unix
下:
var (
status syscall.WaitStatus
rusage syscall.Rusage
pid1 int
e error
)
for {
pid1, e = syscall.Wait4(p.Pid, &status, 0, &rusage)
if e != syscall.EINTR {
break
}
}
进行了 syscall.Wait4
对系统监听,正如"僵死 Zombie(a defunct process) 进程已终止,但进程描述符存在, 直到父进程调用wait4()系统调用后释放",所说一致。
来源:https://www.cnblogs.com/wilburxu/p/15941020.html


猜你喜欢
- 说下思路吧:原图->灰度->根据像素亮度-映射到指定的字符序列中->输出。字符越多,字符变化稠密。效果会更好。如果根据灰度
- 程序中经常需要使用excel文件,批量读取文件中的数据python读取excel文件可以使用xlrd模块pip install xlrd安装
- 1、集合相加a = {1,2,3}b = {3,4,5}print(type(a))print(a|b)2、queryset 符合条件的筛序
- torch.repeat_interleave()函数解析1.函数说明官网:torch.repeat_interleave(),函数说明如下
- 前言在网页应用中,我们经常需要在处理完表单或其它类型的用户输入后,显示一个通知信息给用户。对于这个需求,Django提供了基于Cookie或
- 我就废话不多说了,大家还是直接看代码吧~lt=client.fangjia.district_stat_all_0416dl = dt.fi
- 有一道算法题题目的意思是在二维数组里找到一个峰值。要求复杂度为n。解题思路是找田字(四边和中间横竖两行)中最大值,用分治法递归下一个象限的田
- 上次更新到pygame实现俄罗斯方块游戏(基础篇3)现在继续一、定义玩家类定义玩家类是为了便于进行手动和机器模式或各种不同机器人模式的混合使
- 使用JS技术实现QQ阅读类似的点击展开、收起效果,具体内容如下一、定义展开函数showdiv(),实现点击"全文"按钮,
- 本文为大家分享了路由嵌套的SPA实现的步骤:A(/a)组件需要嵌套B组件(/b)和C组件(/c)①准备嵌套其它组价的父组件 指定一个容器在A
- PDOStatement::closeCursorPDOStatement::closeCursor — 关闭游标,使语句能再次被执行。(P
- 文中给大家介绍MySQL 字符串截取相关函数,具体内容如下所示:在工作中,可能需要将某些字段按某个分割符组成一个字符串作为字段值存取到数据库
- 问题背景VSCode是我们开发go程序的常用工具,但是安装VSCode成功后,创建一个.go文件会有如下提示:这个是vscode提示你需要安
- 1.前言对于数据库引擎来说,内存是一个性能提升的重要解决手段。把数据缓存起来,可以避免在查询或更新数据时花费多余的时间,而这时间通常是从磁盘
- pytorch默认使用单精度float32训练模型,原因在于:使用float16训练模型,模型效果会有损失,而使用double(float6
- SQL SERVER支持的字符串函数内容:LEN(string)函数LOWER(string)函数UPPER (string)函数LTRIM
- 第一步、在detect.py中177行左右,cv.imshow(str(p),im0)之前加上:cv2.putText(im0,f"
- 本文实例讲述了Go语言使用sort包对任意类型元素的集合进行排序的方法。分享给大家供大家参考。具体如下:使用sort包的函数进行排序时,集合
- 对于个人站长来说,如何能使自己的网站与众不同、充满个性,一直是不懈努力的目标。除了尽量提高页面的视觉效果、互动功能以外,如果能在打开网页的同
- CREATETABLE`users`(`id`int(10)NOTNULLAUTO_INCREMENT,`name`char(50)NOTN