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

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

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 &quot;fmt&quot;

func main() {
	producer := make(chan int)
	wait := make(chan int)
	go func() {
		for i := 0; i &lt; 1000; i++ {
			producer &lt;- i
		}
		close(producer)
		wait &lt;- 1
	}()

	go func() {
		count := 0
		for _ = range producer {
			count++
		}
		fmt.Printf(&quot;Consumer 1: %i\n&quot;, count)
	}()

	go func() {
		count := 0
		for _ = range producer {
			count++
		}
		fmt.Printf(&quot;Consumer 2: %i\n&quot;, count)
	}()
	&lt;-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=&quot;amd64&quot;
GOBIN=&quot;&quot;
GOCHAR=&quot;6&quot;
GOEXE=&quot;&quot;
GOHOSTARCH=&quot;amd64&quot;
GOHOSTOS=&quot;darwin&quot;
GOOS=&quot;darwin&quot;
GOPATH=&quot;/usr/local/go/:/Users/victor/Dropbox/projects/go/&quot;
GORACE=&quot;&quot;
GOROOT=&quot;/usr/local/Cellar/go/1.4.2/libexec&quot;
GOTOOLDIR=&quot;/usr/local/Cellar/go/1.4.2/libexec/pkg/tool/darwin_amd64&quot;
CC=&quot;clang&quot;
GOGCCFLAGS=&quot;-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common&quot;
CXX=&quot;clang++&quot;
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)上能够准确重现你的结果。

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 (
	&quot;fmt&quot;
	&quot;runtime&quot;
)

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 &lt; 1000; i++ {
			producer &lt;- i
			runtime.Gosched()
		}
		close(producer)
		wait &lt;- 1
	}()
	go func() {
		count := 0
		for _ = range producer {
			count++
		}
		fmt.Printf(&quot;Consumer 1: %d\n&quot;, count)
		wait &lt;- 1
	}()
	go func() {
		count := 0
		for _ = range producer {
			count++
			runtime.Gosched()
		}
		fmt.Printf(&quot;Consumer 2: %d\n&quot;, count)
		wait &lt;- 1
	}()
	&lt;-wait
	&lt;-wait
	&lt;-wait
}

With yielding (Gosched()):

&gt; go run yield.go
8
8
Consumer 1: 668
Consumer 2: 332
&gt; go run yield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 2: 336
Consumer 1: 664
&gt; go run yield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 2: 333
Consumer 1: 667
&gt;

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:

&gt; go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 1: 81
Consumer 2: 919
&gt; go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 1: 123
Consumer 2: 877
&gt; go run noyield.go
devel +54b4b94 Sat Feb 6 23:33:23 2016 +0000
8
8
Consumer 1: 81
Consumer 2: 919
&gt; 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

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:

确定