82

有没有办法终止在 Golang 中以 os.exec 开始的进程?例如(来自http://golang.org/pkg/os/exec/#example_Cmd_Start),

cmd := exec.Command("sleep", "5")
err := cmd.Start()
if err != nil {
    log.Fatal(err)
}
log.Printf("Waiting for command to finish...")
err = cmd.Wait()
log.Printf("Command finished with error: %v", err)

有没有办法提前终止该过程,也许在 3 秒后?

提前致谢

4

4 回答 4

149

运行并终止exec.Process

// Start a process:
cmd := exec.Command("sleep", "5")
if err := cmd.Start(); err != nil {
    log.Fatal(err)
}

// Kill it:
if err := cmd.Process.Kill(); err != nil {
    log.Fatal("failed to kill process: ", err)
}

超时后运行并终止exec.Process

ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()

if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
    // This will fail after 3 seconds. The 5 second sleep
    // will be interrupted.
}

请参阅Go 文档中的此示例


遗产

在 Go 1.7 之前,我们没有这个context包,这个答案是不同的。

exec.Process使用通道和 goroutine 在超时后运行和终止:

// Start a process:
cmd := exec.Command("sleep", "5")
if err := cmd.Start(); err != nil {
    log.Fatal(err)
}

// Wait for the process to finish or kill it after a timeout (whichever happens first):
done := make(chan error, 1)
go func() {
    done <- cmd.Wait()
}()
select {
case <-time.After(3 * time.Second):
    if err := cmd.Process.Kill(); err != nil {
        log.Fatal("failed to kill process: ", err)
    }
    log.Println("process killed as timeout reached")
case err := <-done:
    if err != nil {
        log.Fatalf("process finished with error = %v", err)
    }
    log.Print("process finished successfully")
}

要么进程结束并且接收到它的错误(如果有的话),done要么已经过去了 3 秒并且程序在完成之前被杀死。

于 2012-08-09T15:32:32.923 回答
22

关于调用的其他答案是正确的Kill(),但是关于在超时后终止进程的部分现在已经过时了。

现在可以使用context包和exec.CommandContext来完成此操作(示例改编自文档中的示例):

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
    defer cancel()

    if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
        // This will fail after 100 milliseconds. The 5 second sleep
        // will be interrupted.
    }
}

从文档:

如果上下文在命令自行完成之前完成,则提供的上下文用于终止进程(通过调用 os.Process.Kill)。

完成后Run(),您可以检查ctx.Err(). 如果达到超时,则返回的错误类型将为DeadLineExceeded。如果是nil,请检查err返回的Run()以查看命令是否完成且没有错误。

于 2019-10-26T16:04:12.243 回答
8

没有选择和通道的更简单的版本。

func main() {
    cmd := exec.Command("cat", "/dev/urandom")
    cmd.Start()
    timer := time.AfterFunc(1*time.Second, func() {
        err := cmd.Process.Kill()
        if err != nil {
            panic(err) // panic as can't kill a process.
        }
    })
    err := cmd.Wait()
    timer.Stop()

    // read error from here, you will notice the kill from the 
    fmt.Println(err)
}

好吧,在咨询了一些有经验的 go 程序员之后,这显然不是解决问题的 GOly 方法。所以请参考接受的答案。


这是一个更短的版本,而且非常直接。但是,如果超时时间很长,可能会有大量挂起的 goroutine。

func main() {
    cmd := exec.Command("cat", "/dev/urandom")
    cmd.Start()
    go func(){
        time.Sleep(timeout)
        cmd.Process.Kill()
    }()
    return cmd.Wait()
}
于 2015-03-04T04:21:25.010 回答
2

虽然exec.CommandContext在大多数情况下非常方便并且工作正常,但我在这个过程中遇到了一些问题'孩子们活着 - 这导致了cmd.Wait()挂起。

如果有人遇到类似情况,这就是我解决问题的方法。

  1. 请求在启动命令之前创建进程组使用Setpgid
  2. 启动一个 go 例程,该例程将在超时时终止进程组

朴素的例子(为了可读性):

cmd := exec.Command("sleep", "5")

// Request the OS to assign process group to the new process, to which all its children will belong
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

go func() {
    time.Sleep(time.Second)
    // Send kill signal to the process group instead of single process (it gets the same value as the PID, only negative)
    syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) 
}

err := cmd.Run()
if err != nil {
    log.Fatal(err)
}
log.Printf("Command finished successfully")

一个更好的例子(对于新的 Gophers 可能不太直观):

    // Create a context with timeout, which will close ctx.Done() channel upon timeout
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel() // Make sure the context is canceled, which will close ctx.Done() channel on function exit
    cmd := exec.Command("sleep", "5")

    // Request the OS to assign process group to the new process, to which all its children will belong
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

    go func() {
        // Wait until timeout or deferred cancellation
        <- ctx.Done()

        // Send kill signal to the process group instead of single process (it gets the same value as the PID, only negative)
        _ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
    }()

    err := cmd.Run()
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("Command finished successfully")

PS 为简洁起见,我将cmd.Start+cmd.Wait替换为cmd.Run

于 2021-06-29T13:46:00.803 回答