如何在 Go 中优雅关闭子进程

有时我们会遇到这样的需求,在一个主进程中启动另外一个进程,而在 Go 中可以使用 exec 包的 Cmd 来轻松实现这类需求,例如代码:

package main

import (
    "fmt"
    "log"
    "os"
    "os/exec"
    "os/signal"
)

func main() {
    cmd := exec.Cmd{
        Path: "nc",
        Args: []string{"-u", "-l", "8888"},
        Dir:  "/usr/bin",
    }

    if err := cmd.Start(); err != nil {
        log.Panic(err)
    }

    fmt.Println("Start child process with pid", cmd.Process.Pid)

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    s := <-c
    fmt.Println("Got signal:", s)
}

这段代码的含义是: 使用 nc -u -l 8888 来模拟一个常驻进程,然后通过 Go 的 exec.Cmd 来运行它,并且 Go 代码不退出,运行代码:


$ go run main.go

Start child process with pid 35904

输出结果表明我们已经通过 Go 成功调用外部命令,起了一个子进程,其进程号为 35904,我们还可以通过命令 ps -ef 35904 来确认:

 UID   PID  PPID   C STIME   TTY           TIME CMD
2062309935 35904 35903   0  3:36PM ttys008    0:00.00 -u -l 8888

如何结束子进程
首先想到的就是 kill 命令,尝试使用 kill 35904

$ kill 35904
$ ps -ef 35904

UID   PID  PPID   C STIME   TTY           TIME CMD
2062309935 35904 35903   0  3:36PM ttys008    0:00.00 (nc)

发现 kill 命令并不好用,进程还在,然后换成 kill -9 也同样不起作用。不过该进程已经停止运行了,可以看到监听由 0:00.00 -u -l 8888 变成了 0:00.00 (nc), 不再监听 8888 端口,只是进程资源还没释放而已。

  • 使用 Go 代码结束该进程
    因为 Go 的 Cmd 内置了 Process.Kill() 函数,我们可以尝试使用它来关闭子进程,修改代码,添加如下内容:
// After five second, kill cmd‘s process
time.Sleep(5 * time.Second)
cmd.Process.Kill()

重新运行代码,发现 5 秒过后,该子进程还在。其实调用 cmd.Process.Kill() 和外部使用 kill 命令是一样的,父进程还没有释放资源,所以子进程不能清理完成。

  • 使用 cmd.Wait() 完成资源清理,修改后的完整代码如下:
package main

import (
    "fmt"
    "log"
    "os"
    "os/exec"
    "os/signal"
    "time"
)

func main() {
    cmd := exec.Cmd{
        Path: "nc",
        Args: []string{"-u", "-l", "8888"},
        Dir:  "/usr/bin",
    }

    if err := cmd.Start(); err != nil {
        log.Panic(err)
    }

    fmt.Println("Start child process with pid", cmd.Process.Pid)

    // Wait releases any resources associated with the Cmd
    go func() {
        if err := cmd.Wait(); err != nil {
            fmt.Printf("Child process %d exit with err: %v\n", cmd.Process.Pid, err)
        }
    }()

    // After five second, kill cmd‘s process
    time.Sleep(5 * time.Second)
    cmd.Process.Kill()

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    s := <-c
    fmt.Println("Got signal:", s)
}

运行代码,可以得到如下结果:

$ go run main.go

Start child process with pid 41666
Child process 41666 exit with err: signal: killed

再通过 ps -el 41666 命令确认子进程 41666 已不存在。

结语

Go 中 exec.Cmd 封装的很好,对于外部命令调用非常方便,但是使用它的时候,需要注意对子进程的资源进行释放,其关键函数就是 cmd.Wait(), 所以用到 cmd 的地方,一定添加 cmd.Wait() 的逻辑。

参考链接:

https://golang.org/pkg/os/exec/#Cmd.Wait

http://www.songjiayang.com/posts/go-zhong-you-ya-guan-bi-zi-jin-cheng

golang 微信交流群请+微信17812796384

原文地址:https://blog.51cto.com/51reboot/2410153

时间: 2024-10-27 16:10:01

如何在 Go 中优雅关闭子进程的相关文章

如何在vi中优雅地使用ex

记得刚开始用vi的时候,只会用:wq或者:q来退出,后来又学会了ZZ,今天上班路上没事做,又把 Learning the Vi & Vim 的 Introducing the ex Editor 过了一遍,又发现了一个退出命令-:x,其实很早以前这些内容都看过,但是由于使用惯性,渐渐地会把一些平时不太用到的命令给遗忘了,其实一个防止遗忘的好办法就是认真总结一下,方便以后查阅. 其实当我们在命令模式下按下冒号后,就已经进入ex编辑模式了,也就是说退出时使用的命令wq q x其实都是ex的命令.vi

如何在Word中优雅的插入Latex线性公式

写论文的小伙伴应该都有过这样的感受!普通二次公式的手动插入如果说是尚可忍受的话,那么做人工智能学习和物理研究的小伙伴在插入二项式定理和傅立叶公式的时候,如果是手动输入....我想不必多说了,下面我就来介绍下,如果配合Mathpix在word中优雅的输入基于Latex的线性公式. LaTeX 作为一款「史诗级」文章排版编译器,一直都有着优秀.高效的排版体验和简洁.一致的排版效果.但是 LaTeX 相对复杂的语法使用,让我们很多时候都需要花费大量时间在查阅 LaTeX 的参考文档上,才能得到我们想要

如何在Vuejs中优雅使用Javascript各种插件

在日常开发中,为了敏捷开发或者更快满足业务需求,不得不使使用js第三方库或者插件. 如何在Vue项目中引入javascript第三方库 全局变量 将 JavaScript 第三方库 添加到项目中,最简单的办法是通过将其附加到 window 对象上,以使其成为全局变量. 如何引入: window._ = require('lodash'); 如何使用: export default { created() { console.log(_.isEmpty() ? 'Lodash everywhere

如何在 Swift 中优雅地处理 JSON

阅读目录 在Swift中使用JSON的问题 开始 基础用法 枚举(Enumeration) 下标(Subscripts) 打印 调试与错误处理 后记 因为Swift对于类型有非常严格的控制,它在处理JSON时是挺麻烦的,因为它天生就是隐式类型.SwiftyJSON是一个能帮助我们在Swift中使用JSON的开源类库.开始之前,让我们先看一下在Swift中处理JSON是多么痛苦. 在Swift中使用JSON的问题 以Twitter API为例.使用Swift,从tweet中取得一个用户的“name

如何在mysql中优雅的解决精确到毫秒的问题?

刚刚搞定,不需要两个字段了.mysql5.6.4以后的版本,支持定义time(3)或者timestamp(6)这样的字段,然后使用 current_timestamp(6) 即可以为该字段赋值带有毫秒或微秒值的时间数据了! 发布于 2015-08-27 我也在研究这个问题.似乎都是两个字段来实现. http://www.zhihu.com/question/20859370 一般搜到的都是用两个字段实现,这是知乎上的说5.6.4之后的可以. 其实也可以用字符串存,而且到毫秒的时间字符串可以做主键

如何在react-native 中优雅的使用 redux

首先说下我对redux 的理解吧,第一印象很重要就像妹纸一样. 一句话来说他就是一个 js 的应用状态容器. 说长点就是当你的应用足够复杂,交互足够多的时候,你不方便管理你的 state, 那么交给 redux 吧,他是一个单向数据流,高效且清晰. 那么什么时候使用 redux呢,有一句话叫当你没有想到redux 的时候,那么你真的可能并不需要它. 下面就简单说下一些基本的概念. Action:中文意思,动作,没错,在 redux 里也是这么个意思,指用户的一个动作,放在 native 里就是

如何在NodeJS项目中优雅的使用ES6

如何在NodeJS项目中优雅的使用ES6 NodeJs最近的版本都开始支持ES6(ES2015)的新特性了,设置已经支持了async/await这样的更高级的特性.只是在使用的时候需要在node后面加上参数:--harmony.但是,即使如此node也还是没有支持全部的ES6特性.所以这个时候就需要用到Babel了. 现在开始Babel 在开始使用Babel之前,假设 1. 你已经安装了nodejs,并且已经熟悉了Js. 2. 你也可以使用npm安装各种依赖包. 3. 而且你也对ES6(后来改为

如何在MySQL中查询每个分组的前几名【转】

问题 在工作中常会遇到将数据分组排序的问题,如在考试成绩中,找出每个班级的前五名等. 在orcale等数据库中可以使用partition语句来解决,但在mysql中就比较麻烦了.这次翻译的文章就是专门解决这个问题的 原文地址: How to select the first/least/max row per group in SQL 翻译 在使用SQL的过程中,我们经常遇到这样一类问题:如何找出每个程序最近的日志条目?如何找出每个用户的最高分?在每个分类中最受欢迎的商品是什么?通常这类"找出每

SO_LINGER和优雅关闭连接

http://blog.csdn.net/ccnyou/article/details/8913334 原文:http://unliminet.blog.51cto.com/380895/346686 当调用closesocket关闭套接字时,SO_LINGER将决定系统如何处理残存在套接字发送队列中的数据.处理方式无非两种:丢弃或者将数据继续发送至对端,优雅关闭连接.事实上,SO_LINGER并不被推荐使用,大多数情况下我们推荐使用默认的关闭方式(即下方表格中的第一种情况). 下方代码段显示l