Go语言中Kill子进程的正确姿势
场景
我们在编写部署系统的时候,通常需要在机器上部署一个agent,用来执行部署脚本,为了防止部署脚本写的有问题,长时间hang住,我们通常会为脚本的执行设置一个超时时间,到了时间之后就kill掉该脚本的进程。如果是Go语言实现,脑袋里应该立马浮现出
os/exec包、cmd.Process.Kill()这样的手段。但是,如果部署脚本中又调用了其他脚本,即子进程又fork出更多子进程的时候,这招就不好使了。
简单来说,就是
cmd.Process.Kill()无法杀死子进程。
知识储备
pstree -g #查看进程树和每个进程的PGID
问题验证
下面我们写段代码来简单验证一下
一般情况下:
package main
import (
"fmt"
"os/exec"
"time"
)
func main() {
cmd := exec.Command("sleep", "5") //睡眠5s
start := time.Now() //记录启动时间
time.AfterFunc(3*time.Second, func() { cmd.Process.Kill() }) //3s后将此进程杀死
err := cmd.Run() //运行该命令
fmt.Printf("pid=%d duration=%s err=%s\n", cmd.Process.Pid, time.Since(start), err) //输出进程ID、运行时间、错误
}
打开终端查看:发现进程能被kill掉
root@flight:~$ ps au
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
root 1804 0.0 0.0 4278124 996 s000 R+ 4:10下午 0:00.00 ps au
didi 1158 0.0 0.0 4296892 1284 s000 S 4:08下午 0:00.03 -bash
didi 1798 0.0 0.0 4270348 564 s001 S+ 4:10下午 0:00.00 sleep 5
root@flight:~$ ps au
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
root 1819 0.0 0.0 4268908 972 s000 R+ 4:10下午 0:00.00 ps au
didi 1158 0.0 0.0 4296892 1300 s000 S 4:08下午 0:00.03 -bash
root@flight:~$
当进程fork出子进程:
package main
import (
"fmt"
"os/exec"
"time"
)
func main() {
cmd := exec.Command("/bin/sh", "-c", "watch date > date.txt") //watch进程fork出了其他子进程
start := time.Now() //记录启动时间
time.AfterFunc(3*time.Second, func() { cmd.Process.Kill() }) //3s后将此进程杀死
err := cmd.Run() //运行该命令
fmt.Printf("pid=%d duration=%s err=%s\n", cmd.Process.Pid, time.Since(start), err) //输出进程ID、运行时间、错误
}
输出结果:发现同样运行了3s被kill
[root@localhost ~]# go run test.go
pid=16860 duration=3.001284491s err=signal: killed #同样运行了3s被kill
[root@localhost ~]#
但是查看用户进程会发现不一样:
[root@localhost ~]# ps -af
UID PID PPID C STIME TTY TIME CMD
root 2409 2277 0 17:07 pts/1 00:00:01 top
root 5118 4953 0 17:09 pts/3 00:00:00 top
root 16804 2269 14 17:14 pts/0 00:00:00 go run test.go
root 16855 16804 0 17:14 pts/0 00:00:00 /tmp/go-build276131739/b001/exe/test
root 16860 16855 0 17:14 pts/0 00:00:00 /bin/sh -c watch date > date.txt
root 16861 16860 0 17:14 pts/0 00:00:00 watch date
root 16954 4919 0 17:14 pts/2 00:00:00 ps -af
[root@localhost ~]#
[root@localhost ~]#
[root@localhost ~]# ps -af
UID PID PPID C STIME TTY TIME CMD
root 2409 2277 0 17:07 pts/1 00:00:01 top
root 5118 4953 0 17:09 pts/3 00:00:00 top
root 16861 1 0 17:14 pts/0 00:00:00 watch date
root 17169 4919 0 17:14 pts/2 00:00:00 ps -af
现象:
- 程序运行3s后
/bin/sh -c watch date > date.txt被kill,但是watch date依然存在。 watch date的父进程是1号进程。
问题原因
Go是使用kill(2)向sh进程的PID发了一个KILL信号,但没有发给watch进程,sh进程被kill之后,导致watch进程变成孤儿进程。
解决方案
kill(2)不但支持向单个PID发送信号,还可以向进程组发信号,我们只要把sh进程及其所有子进程放到一个进程组里,就可以批量Kill了。关键是PGID的设置,默认情况下,子进程会把自己的PGID设置成与父进程相同,所以,我们只要设置了sh进程的PGID,所有子进程也就相应的有了PGID。
注意:传递进程组PGID的时候要使用负数的形式。
注意:下面这种方式适合非su - <user> command执行命令的方式,否则杀死父进程后,子进程将被托管成为孤儿进程。因为他们的PGID不一样,即使设置了cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}也没有用。正确的方式是采用exec包自带的方式来指定执行用户
package main
import (
"fmt"
"os/exec"
"syscall"
"time"
)
func main() {
cmd := exec.Command("/bin/sh", "-c", "watch date > date.txt")
// Go会将PGID设置成与PID相同的值
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Setpgid=true
start := time.Now()
time.AfterFunc(3*time.Second, func() { syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) }) //想要杀死整个进程组,而不是单个进程,需要传递负数形式。
err := cmd.Run()
fmt.Printf("pid=%d duration=%s err=%s\n", cmd.Process.Pid, time.Since(start), err)
}
如果想要指定用户:
sysuser, err := user.Lookup("user1") // 通过用户名来获取用户信息
if err != nil {
fmt.Println(err)
}
uid, err := strconv.Atoi(sysuser.Uid) // 将UID的类型转换成 uint32
if err != nil {
fmt.Println(err)
}
gid, err := strconv.Atoi(sysuser.Gid) // 将GID的类型转换成 uint32
if err != nil {
fmt.Println(err)
}
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
}
参考文档
Go语言中Kill子进程的正确姿势的更多相关文章
- int转换char的正确姿势
一:背景 在一个项目中,我需要修改一个全部由数字(0~9)组成的字符串的特定位置的特定数字,我采用的方式是先将字符串转换成字符数组,然后利用数组的位置来修改对应位置的值.代码开发完成之后,发现有乱码出 ...
- C语言中,头文件和源文件的关系(转)
简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析阶段 3.编译阶段,首先编译成纯汇编语句, ...
- C语言中do...while(0)的妙用(转载)
转载来自:C语言中do...while(0)的妙用,感谢分享. 在linux内核代码中,经常看到do...while(0)的宏,do...while(0)有很多作用,下面举出几个: 1.避免goto语 ...
- C语言中system()函数的用法总结(转)
system()函数功能强大,很多人用却对它的原理知之甚少先看linux版system函数的源码: #include <sys/types.h> #include <sys/wait ...
- C语言中void*详解及应用
void在英文中作为名词的解释为“空虚:空间:空隙”:而在C语言中,void被翻译为“无类型”,相应的void *为“无类型指针”.void似乎只有“注释”和限制程序的作用,当然,这里的“注释”不是为 ...
- (七)C语言中的void 和void 指针类型
许多初学者对C中的void 和void 的指针类型不是很了解.因此常常在使用上出现一些错误,本文将告诉大家关于void 和void 指针类型的使用方法及技巧. 1.首先,我们来说说void 的含义: ...
- 转:C语言中的static变量和C++静态数据成员(static member)
转自:C语言中的static变量和C++静态数据成员(static member) C语言中static的变量:1).static局部变量 a.静态局部变量在函数内定义,生存期为整个程序 ...
- C语言中.h和.c文件解析(很精彩)
C语言中.h和.c文件解析(很精彩) 简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析 ...
- C语言中.h和.c文件解析
整理自C语言中.h和.c文件解析(很精彩) Part.1(林锐<高质量C/C++编程>) 通过头文件来调用库功能.在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的 ...
- C/C++语言中const的用法
1. const 在C和C++中的区别 C++中的const正常情况下是看成编译期的常量,编译器并不为const分配空间,只是在编译的时候将期值保存在名字表中,并在适当的时候折合在代码中. 所 ...
随机推荐
- 迁移 dotnet 6 提示必须将目标平台设置为 Windows 平台
我在迁移一个古老的项目为 .NET 6 框架,但是 VS 提示 error NETSDK1136 如果使用 Windows 窗体或 WPF,或者引用使用 Windows 窗体或 WPF 的项目或包,则 ...
- 深入理解Python协程:从基础到实战
title: 深入理解Python协程:从基础到实战 date: 2024/4/27 16:48:43 updated: 2024/4/27 16:48:43 categories: 后端开发 tag ...
- JS实现下拉框切换和tab标签切换
现在商城网页上会有下拉框切换内容,是如何实现的呢,研究了一天,在调整js代码和查找bug.最终完成了自己想要的效果,我没有写CSS样式,只是实现了基本功能,如果对你有所帮助,可以自己写css,使其更加 ...
- fastposter v2.7.1 紧急发布 电商海报编辑器
fastposter v2.7.1 紧急发布 电商海报编辑器 fastposter海报生成器,电商海报编辑器,电商海报设计器,fast快速生成海报 海报制作 海报开发.二维码海报,图片海报,分享海报, ...
- 前端JavaScript开发风格规范
开发者需要建立和遵守的规范 大致可以划分成这几个方向: 开发流程规范 代码规范 git commit规范 项目文件结构规范 UI设计规范 1. 开发流程规范 这里可能有小伙伴有疑问了,开发流程规范不是 ...
- 使用 Splashtop 启用员工远程访问
使员工进行远程工作似乎是一项耗时.不安全且昂贵的任务.但是,借助 Splashtop,您可以快速.轻松.安全地使您的员工从任何位置以最高 价值远程访问其工作站. 如何使用 Splashtop 启用 ...
- 记录一次对MQTT协议的渗透测试经历
前言 由于工作需要,特意翻查了MQTT的相关漏洞,并一一学习复现,在此做以学习记录,没有恶意,如有抄袭,请私信作者删除. 技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站.服务器等 ...
- opensips开启lua支持
操作系统 :CentOS 7.6_x64 opensips版本 :2.4.9 lua版本:5.1 今天整理下CentOS7环境下opensips2.4.9的lua模块笔记及使用示例,并提供运行效果截图 ...
- 从需求角度介绍PasteSpider(K8S平替部署工具适合于任何开发语言)
你是否被K8S的强大而吸引,我相信一部分人是被那复杂的配置和各种专业知识而劝退,应该还有一部分人是因为K8S太吃资源而放手! 这里介绍一款平替工具PasteSpider,PasteSpider是一款使 ...
- linux curl命令的重要用法:发送GET/POST请求,获取网页内容
curl是一个利用URL规则在命令行下工作的文件传输工具,可以说是一款很强大的http命令行工具.它支持文件的上传和下载,是综合 传输工具,但按传统,习惯称url为下载工具. #使用curl发送GET ...