TL;请转到最后一部分,告诉我你将如何解决这个问题.
今天早上我从Python开始使用Go.我想用不同的命令行参数多次调用Go中的一个关闭源代码的可执行文件,并具有bit的并发性.我生成的代码工作得很好,但是我希望得到您的输入以改进它.因为我还处于早期学习阶段,所以我也会解释一下我的工作流程.
为简单起见,这里假设这个"外部封闭源代码程序"是zenity
,这是一个可以从命令行显示图形消息框的Linux命令行工具.
从GO调用可执行文件
所以,在围棋中,我会这样说:
package main
import "os/exec"
func main() {
cmd := exec.Command("zenity", "--info", "--text='Hello World'")
cmd.Run()
}
这应该能正常工作.请注意,.Run()
在功能上等同于.Start()
后跟.Wait()
.这很好,但是如果我只想执行这个程序一次,那么整个编程工作就不值得了.让我们重复几次.
多次调用可执行文件
现在我已经正常工作了,我想使用自定义命令行参数多次调用我的程序(为了简单起见,这里只调用i
次).
package main
import (
"os/exec"
"strconv"
)
func main() {
NumEl := 8 // Number of times the external program is called
for i:=0; i<NumEl; i++ {
cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")
cmd.Run()
}
}
好的,我们成功了!但是我仍然看不出使用Python …有什么好处这段代码实际上是以串行方式执行的.我有一个多核CPU,我想好好利用一下.因此,让我们使用goroutines添加一些并发性.
Goroutines,或者使我的程序并行的一种方式
a)第一次try :只需在各处添加"GO"即可
让我们重写代码,使其更易于调用和重用,并添加著名的关键字go
:
package main
import (
"os/exec"
"strconv"
)
func main() {
NumEl := 8
for i:=0; i<NumEl; i++ {
go callProg(i) // <--- There!
}
}
func callProg(i int) {
cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")
cmd.Run()
}
没什么!有什么问题吗?所有的Goroutine都是一次执行的.我真的不知道为什么zenity没有被执行,但是AFAIK,在zenity外部程序被初始化之前,Go程序就已经退出了.time.Sleep
的使用证实了这一点:等待几秒钟就足以让Zenity的8实例自动启动.不过,我不知道这是否可以被认为是一个错误.
更糟糕的是,我真正想调用的程序需要一段时间才能自动执行.如果我在我的4核cpu上并行执行该程序的8个实例,它将浪费一些时间进行大量的上下文切换 …我不知道普通的猩猩表现如何,但是exec.Command
will在8个不同的线程中8次emits zenity.更糟糕的是,我想要执行这个程序超过10万次.在Goroutine中一次执行所有这些操作根本不会有效率.不过,我还是想利用我的4核CPU!
b)第二次try :使用Goroutine池
在线资源倾向于建议对这类工作使用sync.WaitGroup
.这种方法的问题在于,您基本上是在使用成批的Goroutine:如果我创建了4个成员的WaitGroup,那么Go程序将等待all个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<NumEl; i++ {
go callProg(i, c)
c <- true // At the NumCoreth iteration, c is blocking
}
}
func callProg(i int, c chan bool) {
defer func () {<- c}()
cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")
cmd.Run()
}
这看起来很难看.频道不是为此目的而设计的:我在利用一个副作用.我喜欢defer
的概念,但我讨厌必须声明一个函数(甚至是lambda)才能从我创建的虚拟通道中弹出一个值.哦,当然,使用虚拟通道本身就很难看.
c)第三次try :在所有子元素都死后死亡
现在我们差不多完成了.我只需要考虑到另一个副作用:在所有zenity弹出窗口关闭之前,Go程序就会关闭.这是因为当循环完成时(在第8次迭代时),没有什么可以阻止程序结束.这一次,sync.WaitGroup
个会很有用.
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<NumEl; i++ {
go callProg(i, c, wg)
c <- true // At the NumCoreth iteration, c is blocking
}
wg.Wait() // Wait for all the children to die
close(c)
}
func callProg(i int, c chan bool, wg *sync.WaitGroup) {
defer func () {
<- c
wg.Done() // Decrease the number of alive goroutines
}()
cmd := exec.Command("zenity", "--info", "--text='Hello from iteration n." + strconv.Itoa(i) + "'")
cmd.Run()
}
好了.
我的问题
- Do you know any other proper way to limit the number of goroutines executed at once?个
我不是指线程;Go如何在内部管理goroutines与此无关.我的意思是限制一次启动goroutine的数量:exec.Command
每次调用它都会创建一个新线程,所以我应该控制调用它的时间.
- Does that code look fine to you?
- Do you know how to avoid the use of a dummy channel in that case?个
我无法说服自己,这样的虚拟频道是可行的.