【问题标题】:Terminating a Process Started with os/exec in Golang在 Golang 中终止以 os/exec 开始的进程
【发布时间】:2012-08-09 15:14:14
【问题描述】:

有没有办法终止在 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 秒后?

提前致谢

【问题讨论】:

    标签: go


    【解决方案1】:

    运行和终止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 docs中的此示例


    旧版

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

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

    // 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 秒并且程序在完成之前被杀死。

    【讨论】:

    • 注意:杀死进程后,Wait()会返回。您应该在 err := cmd.Process.Kill() 之后从 done 中提取以防止内存泄漏。
    • @RhythmicFistman 它将导致 goroutine 挂起,直到程序结束时尝试发送到完成,而没有人收到它。更好的方法是将第一行更改为done := make(chan error, 1)。这将允许发送立即成功,并且 goroutine 无需拉入就退出。
    • @CharlieParker,确保向通道的一次发送始终会立即成功,并且发送方可以继续前进,或者在这种情况下退出。如果没有,那么它将等待有人接收。如果没有人这样做,它将永远等待并保留记忆。
    • @KamilDziedzic,写入 done 的目的是确保程序在继续程序之前退出(等待返回)。在任何情况下,程序都不能在该 select 语句之后继续运行。如果您执行非阻塞写入,则无法保证。
    • @StephenWeinberg 我不明白。它的等待与cmd.Wait() 上的原始代码完全相同,并且写入 done 可能只会在以下情况下失败:a)它有完整的缓冲区(它在发送第一个值之后具有)b)没有等待接收......实际上这意味着可能 goroutine 可能会在 main goroutine 准备好接收之前尝试发送到 done ......所以,是的,缓冲 chan 是必需的。 play.golang.org/p/Mx2gh9zMji 但我还是不明白你为什么说它不会等待 cmd 完成
    【解决方案2】:

    关于调用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,则检查Run()返回的err,看看命令是否完成且没有错误。

    【讨论】:

      【解决方案3】:

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

      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()
      }
      

      【讨论】:

        【解决方案4】:

        虽然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")
        

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

        【讨论】:

          猜你喜欢
          • 2018-12-01
          • 1970-01-01
          • 1970-01-01
          • 2015-08-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-05-19
          相关资源
          最近更新 更多