TL; TR:请转到最后一部分,告诉我您将如何解决此问题。

我今天早上开始使用来自Python的Golang。 我想用不同的命令行参数多次调用Go的封闭源可执行文件,并发一点。 我得到的代码工作得很好,但是我想得到您的意见,以便进行改进。 由于我处于早期学习阶段,因此我还将解释我的工作流程。

为简单起见,在此假定此“外部封闭源程序”是exec.Command,这是一个Linux命令行工具,可以从命令行显示图形消息框。

从Go调用可执行文件

因此,在Go中,我会这样:

package main

import "os/exec"

func main() {

cmd := exec.Command("zenity", "--info", "--text='Hello World'")

cmd.Run()

}

这应该工作正确。 请注意,exec.Command在功能上等效于.Start(),后跟2549413446362334234210。这很棒,但是如果我只想执行一次该程序,那么整个编程工作将是不值得的。 因此,让我们做多次。

多次调用可执行文件

现在,我已经完成了这项工作,我想使用自定义命令行参数多次调用程序(为简单起见,此处仅exec.Command)。

package main

import (

"os/exec"

"strconv"

)

func main() {

NumEl := 8 // Number of times the external program is called

for i:=0; i

cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")

cmd.Run()

}

}

好的,我们做到了! 但是我仍然看不到Go相对于Python的优势。。。这段代码实际上是以串行方式执行的。 我有一个多核CPU,我想利用它。 因此,让我们与goroutines并发一些。

Goroutines,或使我的程序并行化的一种方法

a)首次尝试:只需在所有位置添加“开始”

让我们重写代码以使事情更容易调用和重用,并添加著名的exec.Command关键字:

package main

import (

"os/exec"

"strconv"

)

func main() {

NumEl := 8

for i:=0; i

go callProg(i) //

}

}

func callProg(i int) {

cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")

cmd.Run()

}

没有! 问题是什么? 所有goroutine都立即执行。 我真的不知道为什么不执行zenity,而是执行AFAIK,Go程序在甚至无法初始化zenity外部程序之前就退出了。 使用exec.Command可以证实这一点:等待几秒钟足以让8个zenity实例启动。 我不知道这是否可以视为错误。

更糟糕的是,我实际上想调用的真实程序需要一段时间才能执行。 如果我在4核CPU上并行执行该程序的8个实例,这将浪费一些时间进行大量上下文切换…………我不知道普通Go goroutine的行为如何,但是exec.Command将在8种不同的环境中启动zenity 8次线程。 更糟糕的是,我想执行此程序超过100,000次。 在goroutines中一次执行所有这些操作根本不会有效。 尽管如此,我还是想利用我的4核CPU!

b)第二次尝试:使用goroutines池

在线资源倾向于建议将exec.Command用于此类工作。 这种方法的问题在于,您基本上正在使用一批goroutine:如果我创建了一个由4个成员组成的WaitGroup,则Go程序将等待所有4个外部程序完成,然后再调用一批新的4个程序。 这不是很有效:再次浪费CPU。

其他一些资源建议使用缓冲通道来完成工作:

package main

import (

"os/exec"

"strconv"

)

func main() {

NumEl := 8 // Number of times the external program is called

NumCore := 4 // Number of available cores

c := make(chan bool, NumCore - 1)

for i:=0; i

go callProg(i, c)

c

}

}

func callProg(i int, c chan bool) {

defer func () {

cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")

cmd.Run()

}

这看起来很丑。 渠道并非用于此目的:我正在利用副作用。 我喜欢exec.Command的概念,但是我讨厌必须声明一个函数(甚至是lambda)来从我创建的虚拟通道中弹出一个值。 哦,当然,使用虚拟频道本身很丑陋。

c)第三次尝试:当所有孩子都死后死亡

现在我们快完成了。 我只需要考虑另一个副作用:Go程序在关闭所有zenity弹出窗口之前先关闭。 这是因为在循环完成时(在第8次迭代中),没有什么会阻止程序完成。 这次,exec.Command将很有用。

package main

import (

"os/exec"

"strconv"

"sync"

)

func main() {

NumEl := 8 // Number of times the external program is called

NumCore := 4 // Number of available cores

c := make(chan bool, NumCore - 1)

wg := new(sync.WaitGroup)

wg.Add(NumEl) // Set the number of goroutines to (0 + NumEl)

for i:=0; i

go callProg(i, c, wg)

c

}

wg.Wait() // Wait for all the children to die

close(c)

}

func callProg(i int, c chan bool, wg *sync.WaitGroup) {

defer func () {

wg.Done() // Decrease the number of alive goroutines

}()

cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")

cmd.Run()

}

完成。

我的问题

您是否知道其他任何适当的方法来限制一次执行的goroutine的数量?

我不是说线程。 Go如何在内部管理goroutine无关紧要。 我的意思是限制一次启动的goroutine的数量:exec.Command每次调用时都会创建一个新线程,因此我应该控制它的调用时间。

该代码对您来说看起来不错吗?

您知道在那种情况下如何避免使用虚拟通道吗?

我不能说服自己,要走这样的虚拟渠道。