Golang Go语言中怎么可以在关闭 exec.Command 打开的进程时保证关闭所有的子进程
比如代码这样的
func command(command string, writer io.Writer, notify chan struct{}) {
r, w := io.Pipe()
cmd := exec.Command("bash")
cmd.Dir = "/data"
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} # 这个还不兼容 windows
cmd.Stdin = r
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
defer stdout.Close()
defer stderr.Close()
cmd.Start()
go trans(stdout, writer) // iocopy
go trans(stderr, writer)
go func() {
<-notify
fmt.Println(cmd.Process.Kill())
w.Close()
}()
fmt.Fprint(w, command)
w.Close()
cmd.Wait()
}
如果执行 yarn build
会执行一个 nodejs 的构建,但是当 notify 通知后,bash 进程退出了。nodejs 进程僵尸了。
怎么保证所有由 bash 开启的所有子进程都被关闭(哪怕强制退出)
Golang Go语言中怎么可以在关闭 exec.Command 打开的进程时保证关闭所有的子进程
更多关于Golang Go语言中怎么可以在关闭 exec.Command 打开的进程时保证关闭所有的子进程的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
先找到子进程再关闭。
更多关于Golang Go语言中怎么可以在关闭 exec.Command 打开的进程时保证关闭所有的子进程的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你都知道拿 group id 了,你应该知道用 syscall.Kill 啊
测试的时候貌似不管用。bash 退了然后 yarn 就直接挂到 init 下了还在跑
给 syscall kill 传 group id ,不是 process id
man7.org/linux/man-pages/man2/kill.2.html
If pid is less than -1, then sig is sent to every process in the
process group whose ID is -pid.
传取反以后的 group id ,读读文档就好
之前调查过这个问题,不同操作系统情况不同:
POSIX(Linux 除外)似乎没有通用方法。杀进程组的话,子进程可以创建新的进程组来避免。杀进程树的话,子进程可以 fork 两次来脱离进程树。
Linux 可以用 PID namespace 来实现,当一个 PID namespace 里面的“init”进程退出后,其他进程都会被停止。
Windows 可以用 Job Object 实现。
我记得这个问题曾经让我很头疼,原因是 go 进程开启了 bash ,bash 执行 git ,git 又启动了子进程,但是最终产生了大量的 defunct 状态的进程项,也就是说进程已经销毁了,但是 pid 还留在注册表上,导致最终系统无法再新建进程。
最后的解决方法与 go 没有关系,这个问题似乎是 git 的问题,我起了一个定时任务,定期检查 fefunct 的进程项,然后通过调用 wait 强行把它们从进程表中删除。
为什么要杀子进程?子进程不会正常退出吗?不正常退出要做错误处理吧?硬杀是不是太粗暴了
僵尸要 wait
这是个 deployer 的场景。假设构建需要 10 分钟,设置超时 5 分钟,那确实需要强制 kill
#5
奇怪。今天上班 go run 好了。。之前 nodejs 进程死活杀不掉。。
pgid, _ := syscall.Getpgid(cmd.Process.Pid)
if pgid == -1 {
return
}
syscall.Kill(-pgid, syscall.SIGKILL)
死活杀不掉的看下进程是不是进入 D 状态了
linux:
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
windows:
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP}
这样应该可以?
- https://github.com/microsoft/vscode-js-debug/blob/main/src/targets/node/killTree.ts
- https://github.com/microsoft/vscode-js-debug/blob/main/src/targets/node/terminateProcess.sh
- https://github.com/microsoft/rushstack/blob/main/libraries/node-core-library/src/SubprocessTerminator.ts
- https://github.com/search?q=org%3Amicrosoft+terminateTree+filename%3AterminateProcess.sh
使用以下代码解决,来源我的项目 go-zoox/watch: https://github.com/go-zoox/watch/blob/master/process/process.gogo<br>if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err != nil {<br> return fmt.Errorf("failed to kill process: %s", err)<br>}<br>
相关:
- https://github.com/go-zoox/watch/blob/master/process/process.go
- https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773
不要 kill ,要 wait
在Go语言中,使用exec.Command
启动的进程不会自动终止其子进程。为了确保在关闭主进程时也能关闭所有子进程,你可以采取以下几种策略:
-
使用进程组:在Unix/Linux系统上,可以通过将进程及其子进程放入一个进程组,然后终止整个进程组来实现。具体方法是:
- 在启动进程前,调用
os.Setsid
创建一个新的会话,这会使得该进程成为新会话的领头进程,并脱离原来的进程组。 - 随后启动的子进程将自动成为这个新会话的成员。
- 当需要终止时,向这个会话发送
SIGKILL
信号,可以杀死所有进程组内的进程。
- 在启动进程前,调用
-
手动管理子进程:记录所有启动的子进程PID,并在需要时逐个终止它们。这要求你显式地跟踪所有子进程,并在主进程退出时遍历这些PID,发送终止信号。
-
使用第三方库:有些第三方库(如
os/exec
的扩展库)提供了更高级别的API来处理进程及其子进程的生命周期管理。这些库可能会简化上述过程,但需要注意库的兼容性和维护状态。
在实际应用中,推荐根据具体需求和环境选择最适合的方法。如果跨平台兼容性是首要考虑,可能需要结合使用多种方法,或者考虑在应用架构层面解决进程管理问题。