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チャネルとゴルーチンを使用して、タイムアウト後に実行および終了します。

// 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チェックして、コマンドがエラーなしで完了したかどうかを確認します。errRun()

于 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プログラマーに相談した後、これは明らかに問題を解決するのに十分な方法ではありません。ですので、受け入れられた答えを参照してください。


これはさらに短いバージョンで、非常に簡単です。ただし、タイムアウトが長い場合は、大量のゴルーチンがぶら下がっている可能性があります。

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

もう少し良い例(新しいGopherの場合は直感的ではない可能性があります):

    // 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 に答える