Go命令调用使用CommandContext控制超时

Go 1.7之前,根据超时结束外部流程需要一些工作。 随着上下文包移入标准库,诸如os / exec之类的标准包可以利用其提供的超时和取消功能。

这里先看在Go 1.7之前使用channel控制超时。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
	"bytes"
	"fmt"
	"os/exec"
	"time"
)

func main() {
	// 试用ping来做测试,win平台去掉"-c 2", "-i 1"参数
	cmd := exec.Command("ping", "-c 2", "-i 1", "8.8.8.8")

	// 使用 bytes.Buffer 获取输出
	var buf bytes.Buffer
	cmd.Stdout = &buf

	cmd.Start()

	// 使用一个channel作为完成信号,这样可以使用select条件
	done := make(chan error)
	go func() { done <- cmd.Wait() }()

	// 开始timer
	timeout := time.After(2 * time.Second)

	// select语句允许我们基于哪个channel执行
	
	select {
	case <-timeout:
		// 超时先发生,杀掉进程兵器输出消息
		cmd.Process.Kill()
		fmt.Println("Command timed out")
	case err := <-done:
		// 命令在超时前执行完成,打印输出和错误(如果有错误)
		fmt.Println("Output:", buf.String())
		if err != nil {
			fmt.Println("Non-zero exit code:", err)
		}
	}
}

Go 1.7及以后,使用CommandContext控制超时。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

package main

import (
	"context"
	"fmt"
	"os/exec"
	"time"
)

func main() {
	// 创建一个上下文并且设置超时时间
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	
	// 使用我们的上下文创建命令
	cmd := exec.CommandContext(ctx, "ping", "-c 4", "-i 1", "8.8.8.8")

	// 这次我们可以简单的使用Output()获取输出结果
	out, err := cmd.Output()

	// 我们要通过检查上下文错误来确定超时是否执行
	
	// cmd.Output()返回的错误将特定于操作系统,具体取决于进程被杀死时发生的情况。
	
	if ctx.Err() == context.DeadlineExceeded {
		fmt.Println("Command timed out")
		return
	}

	// 如果没有上下文错误,我们知道命令已经执行完成(或者执行出错)
	fmt.Println("Output:", string(out))
	if err != nil {
		fmt.Println("Non-zero exit code:", err)
	}
}

翻译自:Go: Timeout Commands with os/exec CommandContext 在原文基础上做了些调整。

updatedupdated2020-05-202020-05-20
Load Comments?