英文:
why goroutines executive order is the same between runs
问题
我有一个简单的Go程序,其中有两个消费者通道同时从一个生产者那里读取,代码如下:
package main
import "fmt"
func main() {
producer := make(chan int)
wait := make(chan int)
go func() {
for i := 0; i < 1000; i++ {
producer <- i
}
close(producer)
wait <- 1
}()
go func() {
count := 0
for _ = range producer {
count++
}
fmt.Printf("Consumer 1: %i\n", count)
}()
go func() {
count := 0
for _ = range producer {
count++
}
fmt.Printf("Consumer 2: %i\n", count)
}()
<-wait
}
我原本期望两个消费者获得相同数量的数据,或者至少接近相等。然而,结果总是:
Consumer 1: %!i(int=667)
Consumer 2: %!i(int=333)
这个结果在多次运行中保持不变。有人可以解释一下这种行为吗?
环境:
go version go1.4.2 darwin/amd64
GOARCH="amd64"
GOBIN=""
GOCHAR="6"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/usr/local/go/:/Users/victor/Dropbox/projects/go/"
GORACE=""
GOROOT="/usr/local/Cellar/go/1.4.2/libexec"
GOTOOLDIR="/usr/local/Cellar/go/1.4.2/libexec/pkg/tool/darwin_amd64"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common"
CXX="clang++"
CGO_ENABLED="1"
以上是你要翻译的内容。
英文:
I have a simple go program which has 2 consumer channels reading from one producer at the same time like this:
package main
import "fmt"
func main() {
producer := make(chan int)
wait := make(chan int)
go func() {
for i := 0; i < 1000; i++ {
producer <- i
}
close(producer)
wait <- 1
}()
go func() {
count := 0
for _ = range producer {
count++
}
fmt.Printf("Consumer 1: %i\n", count)
}()
go func() {
count := 0
for _ = range producer {
count++
}
fmt.Printf("Consumer 2: %i\n", count)
}()
<-wait
}
I was expecting two consumers get the same number of data or at least nearly equal. However the result is always:
Consumer 1: %!i(int=667)
Consumer 2: %!i(int=333)
It stays the same between multiple runs. Can anyone explain this behavior for me?
Environment:
go version go1.4.2 darwin/amd64
GOARCH="amd64"
GOBIN=""
GOCHAR="6"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/usr/local/go/:/Users/victor/Dropbox/projects/go/"
GORACE=""
GOROOT="/usr/local/Cellar/go/1.4.2/libexec"
GOTOOLDIR="/usr/local/Cellar/go/1.4.2/libexec/pkg/tool/darwin_amd64"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common"
CXX="clang++"
CGO_ENABLED="1"
答案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)上能够准确重现你的结果。
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.Version())
fmt.Println(runtime.NumCPU())
fmt.Println(runtime.GOMAXPROCS(0))
producer := make(chan int, 100)
wait := make(chan int, 100)
go func() {
for i := 0; i < 1000; i++ {
producer <- i
runtime.Gosched()
}
close(producer)
wait <- 1
}()
go func() {
count := 0
for _ = range producer {
count++
}
fmt.Printf("Consumer 1: %d\n", count)
wait <- 1
}()
go func() {
count := 0
for _ = range producer {
count++
runtime.Gosched()
}
fmt.Printf("Consumer 2: %d\n", count)
wait <- 1
}()
<-wait
<-wait
<-wait
}
使用yield(Gosched()):
> go run yield.go
8
8
Consumer 1: 668
Consumer 2: 332
> go run yield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 2: 336
Consumer 1: 664
> go run yield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 2: 333
Consumer 1: 667
playground链接:https://play.golang.org/p/griwLmsPDf
go1.5.1
1
1
Consumer 1: 674
Consumer 2: 326
go1.5.1
1
1
Consumer 1: 674
Consumer 2: 326
go1.5.1
1
1
Consumer 1: 674
Consumer 2: 326
不使用yield:
> go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 1: 81
Consumer 2: 919
> go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 1: 123
Consumer 2: 877
> go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 1: 81
Consumer 2: 919
> go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 2: 673
Consumer 1: 327
playground链接:https://play.golang.org/p/2KV1B04VUJ
go1.5.1
1
1
Consumer 1: 100
Consumer 2: 900
go1.5.1
1
1
Consumer 1: 100
Consumer 2: 900
go1.5.1
1
1
Consumer 1: 100
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.
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.Version())
fmt.Println(runtime.NumCPU())
fmt.Println(runtime.GOMAXPROCS(0))
producer := make(chan int, 100)
wait := make(chan int, 100)
go func() {
for i := 0; i < 1000; i++ {
producer <- i
runtime.Gosched()
}
close(producer)
wait <- 1
}()
go func() {
count := 0
for _ = range producer {
count++
}
fmt.Printf("Consumer 1: %d\n", count)
wait <- 1
}()
go func() {
count := 0
for _ = range producer {
count++
runtime.Gosched()
}
fmt.Printf("Consumer 2: %d\n", count)
wait <- 1
}()
<-wait
<-wait
<-wait
}
With yielding (Gosched()):
> go run yield.go
8
8
Consumer 1: 668
Consumer 2: 332
> go run yield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 2: 336
Consumer 1: 664
> go run yield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 2: 333
Consumer 1: 667
>
playground: https://play.golang.org/p/griwLmsPDf
go1.5.1
1
1
Consumer 1: 674
Consumer 2: 326
go1.5.1
1
1
Consumer 1: 674
Consumer 2: 326
go1.5.1
1
1
Consumer 1: 674
Consumer 2: 326
For comparison, without yielding:
> go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 1: 81
Consumer 2: 919
> go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 1: 123
Consumer 2: 877
> go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 1: 81
Consumer 2: 919
> go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 2: 673
Consumer 1: 327
playground: https://play.golang.org/p/2KV1B04VUJ
go1.5.1
1
1
Consumer 1: 100
Consumer 2: 900
go1.5.1
1
1
Consumer 1: 100
Consumer 2: 900
go1.5.1
1
1
Consumer 1: 100
Consumer 2: 900
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论