golang os/exec包用法之Kill进程及其子进程

前言&背景

平时在做一些开发时难免要调一些shell脚本或者外部程序,golang提供了exec包很方便的帮我们解决了这个问题。但是当外部程序或者shell脚本夯死就使得我们自身的程序很不稳定。与此同时,当我们已经感知到程序脚本运行出现问题时,我们可能需要立刻对程序进行杀死的操作,但是当我们很自然的想到cmd.Process.Kill()时,我们又遇上了另外一个问题,因为这个操作并没有将子进程kill掉,所以我们的需求就出来了,那就是:

Kill进程及其子进程

首先给出解决方案,那就是kill掉进程组

给出一个有测试和原理分析的相关链接,我这里也来说下具体用法。

cmd := exec.Command("/bin/sh", "-c", "...........")
// Go会将PGID设置成与PID相同的值
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)

这里来解释一下上面三行

  • 新建一个cmd
  • 设置该cmd成为一个新的进程组
  • 调用系统Kill方法杀死整个进程组

至此就完成了进程及其所有子进程的删除。

延伸

上面是一个直接杀死进程的代码,而我们通常的场景是脚本执行超时了,或者外部程序控制要杀死一个进程。针对这样的情况,然后我们再结合上面的代码分析,要杀死进程时传入的是-cmd.Process.Pid-代表是传入的是进程组号,cmd.Process.Pid是具体的值,这里要注意的是cmd.Process是否为nil的问题了。

提供一个使用场景,很多API调用都会执行一些shell脚本,而且每个shell命令的执行都要可控(即就是能保存PID,并且能杀死)。那么如何保存,其实我们就可以用一个map,把cmd指针和本次调用任务保存起来,这里的map需要保持同步,自己封装一下sync.RWMutex就可以了。假设我们的任务每次都是有ID的,这里我们就申请一个这样的map:Data map[int64](*exec.Cmd),再次提醒,要保证同步就要把这里的Datasync.RWMutex封装在一起。

那么问题来了:

如何保证我们杀死的一定是一个存在的并且正在运行的程序?

这里也提供了解决方法:

        for {
            if cmd.Process != nil {
                //加锁map、保存PID、解锁map
                break
            }
            time.Sleep(1 * time.Nanosecond)
        }

你没有看错,就是轮询,只要cmd.Process不为nil,那就证明程序开始运行了。那我们又如何判断程序依旧在运行,没有运行结束呢(我们总不能把一个死了的进程再次杀一会吧,是不是有点不道德,说不定还会引起进程的误杀)?

如下是判断进程是否结束的代码:

        for {
            if cmd.ProcessState != nil {
                //map加锁、删除map中的key、解锁map
                break
            }
            time.Sleep(1 * time.Nanosecond)
        }

判断的依据是cmd.ProcessState是否为nil,若不为nil就代表程序已经运行结束了,并且将结束的相关信息已经保存进cmd.ProcessState

就是这样,以较为优雅的方式控制exec程序的生死 :-)

你可能感兴趣的:(Golang,golang,os/exec,进程)