在后台执行命令

时间:2018-02-01 07:54:42

标签: go concurrency process parallel-processing background

我使用以下代码执行命令" npm install" ,现在调试时我发现该命令大约需要10..15秒来执行(取决于我有多少模块)。我想要的是这个命令将在后台执行,程序将继续。

cmd := exec.Command(name ,args...)
cmd.Dir = entryPath

在调试中,我看到要移动到下一行tass大约10..15秒...

我有两个问题:

  1. 我怎么能这样做?因为我想要并行做某事......
  2. 我怎么知道它什么时候结束?为了提供与该命令相关的附加逻辑,即在完成npm install之后我需要 做其他事情。

2 个答案:

答案 0 :(得分:9)

虽然一般来说你需要goroutine来运行并行(或者更确切地说是并发),但是如果以这种方式运行外部命令或应用程序则不需要你使用goroutines(事实上,这是多余的。)

这是因为用于运行命令的exec.Cmd具有Cmd.Start()方法,该方法启动指定的命令但不等待它完成。因此,当它在后台运行时你可以自由地做其他事情,当你需要等待它完成(并处理它的结果)时,你可以调用Cmd.Wait()(它将阻塞并等待命令到完成)。

这就是它的样子:

cmd := exec.Command("npm", "install", "other_params")
cmd.Dir = entryPath

if err := cmd.Start(); err != nil {
    log.Printf("Failed to start cmd: %v", err)
    return
}

// Do other stuff while cmd runs in background:
log.Println("Doing other stuff...")

// And when you need to wait for the command to finish:
if err := cmd.Wait(); err != nil {
    log.Printf("Cmd returned error: %v", err)
}

Cmd.Start()相反,如果您不需要在“后台”中运行它,则Cmd.Run()会启动指定的命令并等待它完成。事实上,Cmd.Run()只不过是Cmd.Start()Cmd.Wait()来电的链接。

请注意,在“后台”中运行时,为了获取应用的输出,您无法在运行然后命令时调用Cmd.Output()Cmd.CombinedOutput()得到它的输出(你已经启动了命令)。如果需要输出命令,请将缓冲区设置为Cmd.Stdout,然后可以检查/使用。

这是如何做到的:

cmd := exec.Command("npm", "install", "other_params")
cmd.Dir = entryPath
buf := &bytes.Buffer{}
cmd.Stdout = buf

if err := cmd.Start(); err != nil {
    log.Printf("Failed to start cmd: %v", err)
    return
}

// Do other stuff while cmd runs in background:
log.Println("Doing other stuff...")

// And when you need to wait for the command to finish:
if err := cmd.Wait(); err != nil {
    log.Printf("Cmd returned error: %v", err)
    // You may decide to continue or return here...
}

fmt.Println("[OUTPUT:]", buf.String())

如果您还想捕获应用的标准错误流,您可能/必须对Cmd.Stderr执行相同操作。提示:您可以将相同的缓冲区设置为Cmd.StdoutCmd.Stderr,然后您将获得组合输出,这可以按照doc进行保证:

  

如果Stdout和Stderr是同一个作家,并且有一个类型可以与==,
进行比较   一次最多只有一个goroutine会调用Write。

答案 1 :(得分:6)

要并行运行方法,您可以使用goroutineschannels

在goroutine中,执行exec.Command操作。这将在后台工作。

您可以使用goroutine中的频道返回信息。

最后,您可以检查通过频道发送的此值。如果您在致电goroutine后立即检查,它将会阻止,直到从频道收到值。所以你需要在你想要并行完成的其余工作之后检查这个。

type Data struct {
    output []byte
    error  error
}

func runCommand(ch chan<- Data) {
    cmd := exec.Command("ls", "-la")
    data, err := cmd.CombinedOutput()
    ch <- Data{
        error:  err,
        output: data,
    }
}

func main() {
    c := make(chan Data)

    // This will work in background
    go runCommand(c)

    // Do other things here

    // When everything is done, you can check your background process result
    res := <-c
    if res.error != nil {
        fmt.Println("Failed to execute command: ", res.error)
    } else {
        // You will be here, runCommand has finish successfuly
        fmt.Println(string(res.output))
    }
}

请参阅操作: Play Ground

现在,对于OP的要求如何从另一个包中实现它。

  

发表评论:是的,我理解这一点。但是如果我需要从其他包中注册res:=&lt; -c应该如何正确地完成

你可以试试这个: Play Ground 。检查另一个包check

  

注意:此链接不会构建