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

栏目: Go · 发布时间: 4年前

内容简介:有时我们会遇到这样的需求,在一个主进程中启动另外一个进程,而在 Go 中可以使用 exec 包的这段代码的含义是: 使用

如何在  <a href='https://www.codercto.com/topics/6127.html'>Go</a>  中优雅关闭子进程

如何在 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() 的逻辑。

参考链接:


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

代码整洁之道:程序员的职业素养

代码整洁之道:程序员的职业素养

罗伯特·C.马丁 (Robert C.Martin) / 余晟、章显洲 / 人民邮电出版社 / 2016-9-1 / 49.00元

1. 汇聚编程大师40余年编程生涯的心得体会 2. 阐释软件工艺中的原理、技术、工具和实践 3. 助力专业软件开发人员具备令人敬佩的职业素养 成功的程序员在以往的工作和生活中都曾经历过大大小小的不确定性,承受过永无休止的压力。他们之所以能够成功,是因为拥有一个共同点,都深切关注创建软件所需的各项实践。他们将软件开发视为一种需要精雕细琢加以修炼的技艺,他们以专业人士的标准要求自己,......一起来看看 《代码整洁之道:程序员的职业素养》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具