为什么 Goroutines 的执行顺序在每次运行时都是相同的?

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

why goroutines executive order is the same between runs

问题

我有一个简单的Go程序,其中有两个消费者通道同时从一个生产者那里读取,代码如下:

  1. package main
  2. import "fmt"
  3. func main() {
  4. producer := make(chan int)
  5. wait := make(chan int)
  6. go func() {
  7. for i := 0; i < 1000; i++ {
  8. producer <- i
  9. }
  10. close(producer)
  11. wait <- 1
  12. }()
  13. go func() {
  14. count := 0
  15. for _ = range producer {
  16. count++
  17. }
  18. fmt.Printf("Consumer 1: %i\n", count)
  19. }()
  20. go func() {
  21. count := 0
  22. for _ = range producer {
  23. count++
  24. }
  25. fmt.Printf("Consumer 2: %i\n", count)
  26. }()
  27. <-wait
  28. }

我原本期望两个消费者获得相同数量的数据,或者至少接近相等。然而,结果总是:

  1. Consumer 1: %!i(int=667)
  2. Consumer 2: %!i(int=333)

这个结果在多次运行中保持不变。有人可以解释一下这种行为吗?

环境

  1. go version go1.4.2 darwin/amd64
  2. GOARCH="amd64"
  3. GOBIN=""
  4. GOCHAR="6"
  5. GOEXE=""
  6. GOHOSTARCH="amd64"
  7. GOHOSTOS="darwin"
  8. GOOS="darwin"
  9. GOPATH="/usr/local/go/:/Users/victor/Dropbox/projects/go/"
  10. GORACE=""
  11. GOROOT="/usr/local/Cellar/go/1.4.2/libexec"
  12. GOTOOLDIR="/usr/local/Cellar/go/1.4.2/libexec/pkg/tool/darwin_amd64"
  13. CC="clang"
  14. GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common"
  15. CXX="clang++"
  16. CGO_ENABLED="1"

以上是你要翻译的内容。

英文:

I have a simple go program which has 2 consumer channels reading from one producer at the same time like this:

  1. package main
  2. import &quot;fmt&quot;
  3. func main() {
  4. producer := make(chan int)
  5. wait := make(chan int)
  6. go func() {
  7. for i := 0; i &lt; 1000; i++ {
  8. producer &lt;- i
  9. }
  10. close(producer)
  11. wait &lt;- 1
  12. }()
  13. go func() {
  14. count := 0
  15. for _ = range producer {
  16. count++
  17. }
  18. fmt.Printf(&quot;Consumer 1: %i\n&quot;, count)
  19. }()
  20. go func() {
  21. count := 0
  22. for _ = range producer {
  23. count++
  24. }
  25. fmt.Printf(&quot;Consumer 2: %i\n&quot;, count)
  26. }()
  27. &lt;-wait
  28. }

I was expecting two consumers get the same number of data or at least nearly equal. However the result is always:

  1. Consumer 1: %!i(int=667)
  2. Consumer 2: %!i(int=333)

It stays the same between multiple runs. Can anyone explain this behavior for me?

Environment:

  1. go version go1.4.2 darwin/amd64
  2. GOARCH=&quot;amd64&quot;
  3. GOBIN=&quot;&quot;
  4. GOCHAR=&quot;6&quot;
  5. GOEXE=&quot;&quot;
  6. GOHOSTARCH=&quot;amd64&quot;
  7. GOHOSTOS=&quot;darwin&quot;
  8. GOOS=&quot;darwin&quot;
  9. GOPATH=&quot;/usr/local/go/:/Users/victor/Dropbox/projects/go/&quot;
  10. GORACE=&quot;&quot;
  11. GOROOT=&quot;/usr/local/Cellar/go/1.4.2/libexec&quot;
  12. GOTOOLDIR=&quot;/usr/local/Cellar/go/1.4.2/libexec/pkg/tool/darwin_amd64&quot;
  13. CC=&quot;clang&quot;
  14. GOGCCFLAGS=&quot;-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common&quot;
  15. CXX=&quot;clang++&quot;
  16. CGO_ENABLED=&quot;1&quot;

答案1

得分: 2

Go的goroutine调度算法在《Go编程语言规范》中没有定义。它是未定义的,因此它的实现和版本是依赖于具体情况的。当前的Go实现使用了一种协作调度方案。协作调度方案依赖于goroutine定期让出执行权给调度器。调度器的代码位于runtime包中。

该程序依赖于Go通道操作。

该程序还依赖于硬件(例如CPU数量)、操作系统和其他正在运行的程序。

你的代码不应该对goroutine调度有特定的期望。

Go 1.4默认使用GOMAXPROCS(1);对于Go 1.5及更高版本,默认使用NumCPU()。

我修改了你的程序来修复错误(添加了额外的等待语句)、显示诊断信息,并在某些点上让出执行权给调度器(Gosched())。现在,该程序在Go 1.6(开发版本)、NumCPU() == 8、GOMAXPROCS(8)以及Go Playground上的Go 1.5.1、NumCPU() == 1、GOMAXPROCS(1)上能够准确重现你的结果。

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. )
  6. func main() {
  7. fmt.Println(runtime.Version())
  8. fmt.Println(runtime.NumCPU())
  9. fmt.Println(runtime.GOMAXPROCS(0))
  10. producer := make(chan int, 100)
  11. wait := make(chan int, 100)
  12. go func() {
  13. for i := 0; i < 1000; i++ {
  14. producer <- i
  15. runtime.Gosched()
  16. }
  17. close(producer)
  18. wait <- 1
  19. }()
  20. go func() {
  21. count := 0
  22. for _ = range producer {
  23. count++
  24. }
  25. fmt.Printf("Consumer 1: %d\n", count)
  26. wait <- 1
  27. }()
  28. go func() {
  29. count := 0
  30. for _ = range producer {
  31. count++
  32. runtime.Gosched()
  33. }
  34. fmt.Printf("Consumer 2: %d\n", count)
  35. wait <- 1
  36. }()
  37. <-wait
  38. <-wait
  39. <-wait
  40. }

使用yield(Gosched()):

  1. > go run yield.go
  2. 8
  3. 8
  4. Consumer 1: 668
  5. Consumer 2: 332
  6. > go run yield.go
  7. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  8. 8
  9. 8
  10. Consumer 2: 336
  11. Consumer 1: 664
  12. > go run yield.go
  13. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  14. 8
  15. 8
  16. Consumer 2: 333
  17. Consumer 1: 667

playground链接:https://play.golang.org/p/griwLmsPDf

  1. go1.5.1
  2. 1
  3. 1
  4. Consumer 1: 674
  5. Consumer 2: 326
  6. go1.5.1
  7. 1
  8. 1
  9. Consumer 1: 674
  10. Consumer 2: 326
  11. go1.5.1
  12. 1
  13. 1
  14. Consumer 1: 674
  15. Consumer 2: 326

不使用yield:

  1. > go run noyield.go
  2. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  3. 8
  4. 8
  5. Consumer 1: 81
  6. Consumer 2: 919
  7. > go run noyield.go
  8. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  9. 8
  10. 8
  11. Consumer 1: 123
  12. Consumer 2: 877
  13. > go run noyield.go
  14. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  15. 8
  16. 8
  17. Consumer 1: 81
  18. Consumer 2: 919
  19. > go run noyield.go
  20. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  21. 8
  22. 8
  23. Consumer 2: 673
  24. Consumer 1: 327

playground链接:https://play.golang.org/p/2KV1B04VUJ

  1. go1.5.1
  2. 1
  3. 1
  4. Consumer 1: 100
  5. Consumer 2: 900
  6. go1.5.1
  7. 1
  8. 1
  9. Consumer 1: 100
  10. Consumer 2: 900
  11. go1.5.1
  12. 1
  13. 1
  14. Consumer 1: 100
  15. Consumer 2: 900

以上是你要翻译的内容。

英文:

The Go goroutine scheduling algorithm is not defined in The Go Programming Language Specification. It's undefined and, therefore, it's implementation and version dependent. Current implementations of Go use a cooperative scheduling scheme. The cooperative scheduling scheme relies on the goroutines to perform operations which yield to the scheduler from time to time. The scheduler code is in package runtime.

The program is dependent on Go channel operations.

The program is also dependent on the hardware (for example, number of CPUs), the operating system, and other running programs.

Your code should not expect a particular distribution from goroutine scheduling.

Go 1.4 defaults to GOMAXPROCS(1); for Go 1.5 and later, it defaults to NumCPU().

I've modified your program to fix bugs (additional wait statements), display diagnostic information, and yield to the scheduler (Gosched()) at certain points. Now, the program closely reproduces your results on Go 1.6 (devel tip), NumCPU() == 8, GOMAXPROCS(8) and on Go Playround, Go 1.5.1, NumCPU() == 1, GOMAXPROCS(1). Yielding to the goroutine scheduler at certain points in tight loops, and not at other points, was the key to reproducing your results.

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;runtime&quot;
  5. )
  6. func main() {
  7. fmt.Println(runtime.Version())
  8. fmt.Println(runtime.NumCPU())
  9. fmt.Println(runtime.GOMAXPROCS(0))
  10. producer := make(chan int, 100)
  11. wait := make(chan int, 100)
  12. go func() {
  13. for i := 0; i &lt; 1000; i++ {
  14. producer &lt;- i
  15. runtime.Gosched()
  16. }
  17. close(producer)
  18. wait &lt;- 1
  19. }()
  20. go func() {
  21. count := 0
  22. for _ = range producer {
  23. count++
  24. }
  25. fmt.Printf(&quot;Consumer 1: %d\n&quot;, count)
  26. wait &lt;- 1
  27. }()
  28. go func() {
  29. count := 0
  30. for _ = range producer {
  31. count++
  32. runtime.Gosched()
  33. }
  34. fmt.Printf(&quot;Consumer 2: %d\n&quot;, count)
  35. wait &lt;- 1
  36. }()
  37. &lt;-wait
  38. &lt;-wait
  39. &lt;-wait
  40. }

With yielding (Gosched()):

  1. &gt; go run yield.go
  2. 8
  3. 8
  4. Consumer 1: 668
  5. Consumer 2: 332
  6. &gt; go run yield.go
  7. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  8. 8
  9. 8
  10. Consumer 2: 336
  11. Consumer 1: 664
  12. &gt; go run yield.go
  13. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  14. 8
  15. 8
  16. Consumer 2: 333
  17. Consumer 1: 667
  18. &gt;

playground: https://play.golang.org/p/griwLmsPDf

  1. go1.5.1
  2. 1
  3. 1
  4. Consumer 1: 674
  5. Consumer 2: 326
  6. go1.5.1
  7. 1
  8. 1
  9. Consumer 1: 674
  10. Consumer 2: 326
  11. go1.5.1
  12. 1
  13. 1
  14. Consumer 1: 674
  15. Consumer 2: 326

For comparison, without yielding:

  1. &gt; go run noyield.go
  2. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  3. 8
  4. 8
  5. Consumer 1: 81
  6. Consumer 2: 919
  7. &gt; go run noyield.go
  8. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  9. 8
  10. 8
  11. Consumer 1: 123
  12. Consumer 2: 877
  13. &gt; go run noyield.go
  14. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  15. 8
  16. 8
  17. Consumer 1: 81
  18. Consumer 2: 919
  19. &gt; go run noyield.go
  20. devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
  21. 8
  22. 8
  23. Consumer 2: 673
  24. Consumer 1: 327

playground: https://play.golang.org/p/2KV1B04VUJ

  1. go1.5.1
  2. 1
  3. 1
  4. Consumer 1: 100
  5. Consumer 2: 900
  6. go1.5.1
  7. 1
  8. 1
  9. Consumer 1: 100
  10. Consumer 2: 900
  11. go1.5.1
  12. 1
  13. 1
  14. Consumer 1: 100
  15. Consumer 2: 900

huangapple
  • 本文由 发表于 2016年2月7日 15:17:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/35250846.html
匿名

发表评论

匿名网友

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

确定