为什么goroutines行政命令在运行之间是相同的

时间:2016-02-07 07:17:55

标签: go concurrency goroutine

我有一个简单的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"

1 个答案:

答案 0 :(得分:2)

Go Go编程语言规范中未定义Go goroutine调度算法。它未定义,因此,它的实现和版本依赖。 Go的当前实现使用协作调度方案。协作调度方案依赖于goroutine来执行不时向调度器屈服的操作。调度程序代码在包运行时中。

该程序取决于Go通道操作。

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

您的代码不应期望从goroutine调度进行特定分发。

将1.4默认设置为GOMAXPROCS(1);对于Go 1.5及更高版本,它默认为NumCPU()。

我已经修改了你的程序以修复错误(附加的等待语句),显示诊断信息,并在某些点上屈服于调度程序(Gosched())。现在,该程序在Go 1.6(开发提示),NumCPU()== 8,GOMAXPROCS(8)和Go Playround,Go 1.5.1,NumCPU()== 1,GOMAXPROCS(1)上密切再现您的结果。在紧密循环中的某些点而不是在其他点处向goroutine调度程序屈服,是重现结果的关键。

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
}

屈服(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

为了比较,不屈服:

> 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