Reputation: 2175
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"
Upvotes: 2
Views: 156
Reputation: 166588
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
Upvotes: 2