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.Commandwill在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?

我无法说服自己,这样的虚拟频道是可行的.

推荐答案

我会产生4个从公共通道读取任务的工作人员大猩猩.比其他通道更快的Goroutine(因为它们的调度方式不同,或者恰好得到简单的任务)将比其他通道接收到更多的任务.除此之外,我会用sync.WaitGroup来等待所有工人完成.剩下的部分只是任务的创建.您可以在这里看到该方法的示例实现:

package main

import (
    "os/exec"
    "strconv"
    "sync"
)

func main() {
    tasks := make(chan *exec.Cmd, 64)

    // spawn four worker goroutines
    var wg sync.WaitGroup
    for i := 0; i < 4; i++ {
        wg.Add(1)
        go func() {
            for cmd := range tasks {
                cmd.Run()
            }
            wg.Done()
        }()
    }

    // generate some tasks
    for i := 0; i < 10; i++ {
        tasks <- exec.Command("zenity", "--info", "--text='Hello from iteration n."+strconv.Itoa(i)+"'")
    }
    close(tasks)

    // wait for the workers to finish
    wg.Wait()
}

可能还有其他可能的方法,但我认为这是一个非常干净的解决方案,很容易理解.

Go相关问答推荐

golang-jWT签名无效

在字符串与字符串子切片上使用len进行位转移产生意外输出

即使HTTP服务器正在使用GO和Protobuf、SQL Server启动,请求也不返回结果

如何在VSCode中为特定的.go文件创建调试配置?

GORM:一个表的两个外键

显示GUI时后台处理功能

nixOS 上的 Nginx 反向代理在try 加载 css/js 时返回 404

如何绕过深层 xml,没有嵌套循环?

使用GOTK3和librsvg在Go中如何加载内联SVG?

Golang 发送Post请求出现400错误

在 .go 文件中运行一个函数和在 Go 模板中调用它有什么区别?

Gorm 预加载给出了模糊的列错误

如何使用带有方法的字符串枚举作为通用参数?

获取切片元素的地址是否意味着 Go 中元素的副本?

具有两个或多个模型的 GORM 查询

无法在 GORM 中排序

即使一个测试用例失败,如何运行所有测试用例

Grafana/Prometheus 将多个 ip 可视化为查询

如何从 docker-compose 命令运行 2 个不同的命令:

在 Go 中,为什么 exec.Command() 失败但 os.StartProcess() 成功启动winget.exe?