你会如何定义一个 goroutines 来一次执行?
技术问答
314 人阅读
|
0 人回复
|
2023-09-11
|
今天早上,我开始用 Python 的 Go。我想从 Go 多次调用封闭源代码的可执行文件,包括一点并发,有不同的命令行参数。我生成的代码运行得很好,但我想得到你的建议来改进它。因为我正处于学习的早期阶段,我将解释我的工作过程。: C2 Y% Q8 v1 @; I$ l
假设这个外部闭源程序是简单起见的zenity一个 Linux 命令行工具,可以从命令行显示图形消息框。) I1 N2 o- K' ^! o( i2 I
从 Go 调用可执行文件所以,在 Go 中,我会这样做:" e8 Z# a. l0 b8 Q2 i& x
package mainimport "os/exec"func main() cmd := exec.Command("zenity","--info","--text='Hello World'") cmd.Run()}5 N3 A8 T c! w' }
这应该工作得当。请注意,这个.Run()等同于一个功能.Start()后跟.Wait()。这很好,但如果我只想执行一次程序,那么整个程序就不值得了。所以让我们做很多次。
; e+ Y$ w7 H: Z* U( i( _多次调用可执行文件现在我有了这份工作,我想用自定义命令参数多次调用我的程序(这里只是i简单起见)。
% `/ r$ u# {' Y' i[code]package main import "os/exec" "strconv")func main() NumEl := 8 // Number of times the external program is called for i:=0; i好吧,我们做到了!但还是看不见Go over Python的优势……这个代码实际上是串行执行的。我有多核 CPU,我想用它。所以让我们用 goroutines 添加一些并发性。
/ T7 F; {% }* {% G' e1 a0 d6 |/ pGoroutines,或者一种让我的程序并行的方法a) 第一次尝试:只需在任何地方添加go”让我们重写代码,使调用和重用更容易,并添加名称go关键字:+ Q4 Q3 U7 l+ U" B; Q: m1 ]* {% j
[code]package mainimport "os/exec" "strconv")func main() { NumEl := 8 for i:=0; i没有什么!问题是什么?goroutine 都是一次性执行的。真不知道为什么不执行 zenity 但 AFAIK,Go 程序在 zenity 外部程序甚至可以初始化之前就退出了。这通过使用time.Sleep: 等几秒钟就足够了zenity 8 实例自行启动。我不知道这是否可以被视为错误。4 W6 c) F' U) \
更糟糕的是,我真正想调用的真正程序需要一段时间才能执行。如果我在我的 4 核 CPU 8 这个程序并行执行的例子会浪费一些时间做很多上下文切换……我不知道普通 Go但是exec.Command 会在 8 的不同线程中启动 zenity 8 次. 更糟糕的是,我想执行这个程序超过 100次。goroutines 一次完成所有这些工作都没有效率。然而,我想用我的 4 核 CPU!
8 c" G6 F4 ]# z" A0 V) }b)第二次尝试:使用 goroutines 池网络资源倾向于推荐sync.WaitGroup用于这种工作。这种方法的问题是,你基本上是在批量处理 goroutine:如果我创建了 4 成员 WaitGroup,Go 程序将等待所有完成4 外部程序,然后调用新批新批程序。效率低下:CPU 又被浪费了。
2 y9 _: J; K e, t" E' `建议使用缓冲通道完成其他资源:. Y6 [% j& `8 r4 Z0 Q0 @( e: b
[code]package mainimport "os/exec" "strconv")func main() NumEl := 8 Number of times the external program is called NumCore := Number of available cores c := make(chan bool,NumCore - 1) for i:=0; i这看起来很丑。频道不是为了这个目的:我正在使用副作用。我喜欢这个概念,defer但是我讨厌一个函数(甚至一个 )lambda)来从我创建的虚拟通道中弹出一个值。哦,当然,使用虚拟通道本身就是丑陋的。3 K- M: B2 P0 F
c) 第三次尝试:当所有的孩子都死了现在我们快完成了。我只需要考虑另一个副作用:Go 程序在所有 zenity 弹出窗口关闭前关闭。这是因为没有什么可以阻止程序在循环结束时完成有什么可以阻止程序完成。这一次,sync.WaitGroup将是有用的。3 V& @" G3 ~: P! P' M
[code]package mainimport "os/exec" "strconv" "sync")func main() NumEl := 8 Number of times the external program is called NumCore := 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完毕。
% Z# {! f8 s5 L我的问题你知道限制一次执行的任何其他适当方法 goroutines 的数量?我不指线程;Go 如何内部管理 goroutines 没关系。我的真正意思是限制一次启动 goroutines 的数量:exec.Command每次调用都会创建一个新的线程,所以我应该控制它被调用的次数。1 n( ?+ \& D( t! x( v& g' }
你觉得这个代码好吗?
0 y* i# ]# z; E+ |8 ?. d" t你知道在这种情况下如何避免使用虚拟通道吗?我无法说服自己,这样的虚拟频道就是要走的路。
. D3 ?' r' ? L0 W' w. V( h9 n
: W, z: h. F; R4 N: k 解决方案:
% L7 S3 h3 j* D) q: K# E/ t 我将从公共通道生成 4 读取任务goroutine。比其他协程快(因为它们的调度方式不同或者碰巧得到简单的任务)会比其他协程收到更多的任务。另外,我会用的sync.WaitGroup等待所有工人完成。剩下的只是任务的创建。您可以在这里查看该方法的示例:2 Y& Q: V+ T) X! B/ b, `0 _, a# V- _7 W
[code]package mainimport "os/exec" "strconv" "sync")func main() tasks := make(chan *exec.Cmd,64) spawn four worker goroutines var wg sync.WaitGroup for i := 0; i 可能还有其他可能的方法,但我认为这是一个非常干净的解决方案,很容易理解。 |
|
|
|
|
|