goroutines导致严重的减速和头痛问题

huangapple go评论110阅读模式
英文:

goroutines causing major slowdowns and headaches

问题

我在使用goroutines时遇到了一些问题。为什么这段代码在顺序执行时需要大约125毫秒才能执行完:

  1. package main
  2. import (
  3. "os/exec"
  4. "time"
  5. "fmt"
  6. )
  7. func main() {
  8. cmd := exec.Command("lessc", "--yui-compress", "test.less")
  9. n := 2000
  10. start := time.Now()
  11. for i := 0; i < n; i++ {
  12. cmd.Run()
  13. }
  14. finish := time.Now()
  15. fmt.Printf("程序运行时间为 %v\n", finish.Sub(start))
  16. }

而当使用goroutines进行并发执行时,这段代码需要大约20秒:

  1. package main
  2. import (
  3. "os/exec"
  4. "time"
  5. "fmt"
  6. )
  7. func main() {
  8. cmd := exec.Command("lessc", "--yui-compress", "test.less")
  9. ch := make(chan bool)
  10. n := 2000
  11. start := time.Now()
  12. for i := 0; i < n; i++ {
  13. go lessc(ch, cmd)
  14. }
  15. fmt.Println(n, "个goroutines已启动。")
  16. for i := 0; i < n; i++ {
  17. _ = <-ch
  18. }
  19. finish := time.Now()
  20. fmt.Printf("程序运行时间为 %v\n", finish.Sub(start))
  21. }
  22. func lessc(ch chan bool, c *exec.Cmd) {
  23. c.Run()
  24. ch <- true
  25. }

在i7 720QM (4C/8T) 8GB RAM linux/x86-64上使用go 1.0.3进行编译和测试,也在相同的机器上使用1.0.2进行了构建和测试,结果出现了相同的问题。

编辑:由@jnml解决。如果有人关心新的修复并发代码,这里是:

  1. package main
  2. import (
  3. "os/exec"
  4. "time"
  5. "fmt"
  6. )
  7. func main() {
  8. ch := make(chan bool)
  9. n := 2000
  10. start := time.Now()
  11. for i := 0; i < n; i++ {
  12. go lessc(ch)
  13. }
  14. fmt.Println(n, "个goroutines已启动。")
  15. for i := 0; i < n; i++ {
  16. _ = <-ch
  17. }
  18. finish := time.Now()
  19. fmt.Printf("程序运行时间为 %v\n", finish.Sub(start))
  20. }
  21. func lessc(ch chan bool) {
  22. cmd := exec.Command("lessc", "--yui-compress", "test.less")
  23. cmd.Run()
  24. ch <- true
  25. }
英文:

I'm having something of a problem with goroutines. Why is it that this code executes in ~125ms (note sequential execution):

  1. package main
  2. import (
  3. &quot;os/exec&quot;
  4. &quot;time&quot;
  5. &quot;fmt&quot;
  6. )
  7. func main() {
  8. cmd := exec.Command(&quot;lessc&quot;, &quot;--yui-compress&quot;, &quot;test.less&quot;)
  9. n := 2000
  10. start := time.Now()
  11. for i := 0; i &lt; n; i++ {
  12. cmd.Run()
  13. }
  14. finish := time.Now()
  15. fmt.Printf(&quot;Program took %v to run\n&quot;, finish.Sub(start))
  16. }

When this code takes about 20 seconds (concurrent execution using goroutines):

  1. package main
  2. import (
  3. &quot;os/exec&quot;
  4. &quot;time&quot;
  5. &quot;fmt&quot;
  6. )
  7. func main() {
  8. cmd := exec.Command(&quot;lessc&quot;, &quot;--yui-compress&quot;, &quot;test.less&quot;)
  9. ch := make(chan bool)
  10. n := 2000
  11. start := time.Now()
  12. for i := 0; i &lt; n; i++ {
  13. go lessc(ch, cmd)
  14. }
  15. fmt.Println(n, &quot; goroutines started.&quot;)
  16. for i := 0; i &lt; n; i++ {
  17. _ = &lt;-ch
  18. }
  19. finish := time.Now()
  20. fmt.Printf(&quot;Program took %v to run\n&quot;, finish.Sub(start))
  21. }
  22. func lessc(ch chan bool, c *exec.Cmd) {
  23. c.Run()
  24. ch &lt;- true
  25. }

Using go 1.0.3 on i7 720QM (4C/8T) 8GB RAM linux/x86-64
Also built and tested using 1.0.2 and got the same problem on the same machine.

Edit: Solved by @jnml below. If anyone cares about the new fixed concurrent code here it is:

  1. package main
  2. import (
  3. &quot;os/exec&quot;
  4. &quot;time&quot;
  5. &quot;fmt&quot;
  6. )
  7. func main() {
  8. ch := make(chan bool)
  9. n := 2000
  10. start := time.Now()
  11. for i := 0; i &lt; n; i++ {
  12. go lessc(ch)
  13. }
  14. fmt.Println(n, &quot; goroutines started.&quot;)
  15. for i := 0; i &lt; n; i++ {
  16. _ = &lt;-ch
  17. }
  18. finish := time.Now()
  19. fmt.Printf(&quot;Program took %v to run\n&quot;, finish.Sub(start))
  20. }
  21. func lessc(ch chan bool) {
  22. cmd := exec.Command(&quot;lessc&quot;, &quot;--yui-compress&quot;, &quot;test.less&quot;)
  23. cmd.Run()
  24. ch &lt;- true
  25. }

答案1

得分: 11

我认为你的程序不正确。它包含了竞态条件,因此可以做任何事情。它的任何时间都没有意义。

你创建了一个exec.Cmd,然后并发(==数据竞争)从多个goroutine中执行它的Run方法。exec.Cmd从未提到它可以被重用来运行多次 - 即使是串行地。

exec.Cmdexec.Command初始化了一些状态,并在执行Run后有不同的状态。也就是说,在执行Run方法后,状态不再初始化,可能不适合另一个Run

英文:

IMO your program is not correct. It contains a race condition and thus can do literally anything. Any timing of it don't make sense.

You're creating one exec.Cmd and then concurrently (== data race) perform its Run method from several goroutines. exec.Cmd never mentions it could be reused for more than once Run - even if serially.

exec.Cmd has some state initialized by exec.Command and a different state after executing Run. IOW, after executing the Run method, the state is not anymore initialized and probably not fit for another Run

huangapple
  • 本文由 发表于 2013年4月17日 18:20:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/16057524.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定