我正在测试阻塞操作如何在 Go 上工作,以及它如何剥夺其他 go-routines 共享处理器,所以我做了那个测试:
package main
import (
"fmt"
"runtime"
"time"
)
func test2() {
for i := 1; ; i++ {
fmt.Println(i, time.Now())
}
}
func test() {
a := 100
for i := 1; i < 1000; i++ {
a = i*100/i + a
fmt.Println("value: " , a )
}
}
func main() {
runtime.GOMAXPROCS(1)
go test2()
for {
test()
}
}
正如您在示例中看到的,main()
的第一行我将 Go 设置为使用单核并且运行一个永无止境的任务,这样它会阻塞任何其他进程,虽然我看到了我没想到的结果,但我发现测试和 test2
都在运行,每个都有它的时间共享(每个进程的时间共享比我设置 时更长) GOMAXPROCS
到更高的值)。这是输出:
https://gist.github.com/anonymous/b3634be74d30fd36f552
我该如何解释?
附言我正在使用 Go version 1.5.3
更新
我通过将 GOMAXPROCS
设置为 2 做了一些更改,从测试函数中删除了 fmt.Println("value: ", a )
,现在,程序运行 test2
一段时间,test
函数接管,没有其他运行!
最佳答案
一个重要的区别是 GOMAXPROCS=1
不会将运行时限制为 1 个内核,它会将主动运行用户代码的操作系统线程数限制为 1。所有(gc 派生的)Go 程序都是多线程。
在大多数情况下,繁忙的循环会干扰调度程序和/或垃圾收集,有时即使 GOMAXPROCS > 1
也是如此。不过在您的示例中,对 test()
和 fmt.Println
函数的调用是调度点,它们允许运行时调度程序执行其他 goroutine。
正如您所指出的,如果从 test
中删除对 fmt.Println
的调用,则 test2()
的进度最终会停止(显然,仅调用 test()
不足以让调度程序继续进行。可能是为了快速、内联、被内部 for 循环阻塞,等等)。
在这种情况下,这是因为对 test()
的繁忙循环调用阻止了 GC 执行垃圾收集的停止世界阶段,并阻塞了调度程序。您可以通过使用 GOGC=off
运行程序来验证这一点。
调度、cpu 绑定(bind)任务、垃圾收集等细节可能会因版本而异,但由于 goroutines 是协作调度的,所以忙循环总是错误的。如果您确实需要长时间运行的 cpu 绑定(bind)循环,您可以通过偶尔插入对 runtime.Gosched()
的调用来与调度程序合作。屈服于其他协程。
关于go - 当设置为具有阻塞操作的单核时,在 Go 中实现并发,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35509753/