英文:
Concurrency is achieved in Go when setting to single core with blocking operations
问题
我正在测试Go语言中的阻塞操作以及它如何剥夺其他goroutine的处理器共享。所以我进行了以下测试:
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语言只使用一个核心,并且运行一个无限循环的任务,以阻塞其他进程。然而,我发现了一些我没有预料到的结果,我发现test
和test2
两个函数都在运行,每个函数都有它自己的时间片(每个进程的时间片比将GOMAXPROCS
设置为更高值时更长)。这是输出结果:
https://gist.github.com/anonymous/b3634be74d30fd36f552
我该如何解释这个现象?
P.S. 我使用的是Go版本1.5.3
更新
我做了一个小改动,将GOMAXPROCS
设置为2,从test
函数中移除了fmt.Println("value: ", a)
,现在程序运行一段时间后,test2
函数运行,然后test
函数接管,没有其他东西运行!
英文:
I was testing how a blocking operation works on Go, and how it deprives other go-routines from having processor share, so I did that test:
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()
}
}
As you can see in the example, first line of main()
I'm setting Go to use a single core and a never ending task runs on, so that it blocks any other process, though I see results I didn't expect, I found out that both test and test2
are running, each having it's time share (a larger time share per process that's longer than if I set the GOMAXPROCS
to higher values). This is the output:
https://gist.github.com/anonymous/b3634be74d30fd36f552
How can I explain this?
P.S. I'm using Go version 1.5.3
Update
I made a little change by setting GOMAXPROCS
to 2, removed fmt.Println("value: " , a )
from test function, now, the program runs test2
for some time and, the test
function takes over and nothing else run!
答案1
得分: 4
一个重要的区别是,GOMAXPROCS=1
并不限制运行时只使用一个核心,它限制的是同时运行用户代码的操作系统线程数量为1。所有(由gc派生的)Go程序都是多线程的。
在大多数情况下,忙碌循环会干扰调度器和/或垃圾回收,有时即使GOMAXPROCS > 1
也是如此。然而,在你的例子中,对test()
和fmt.Println
函数的调用是调度点,这些调度点允许运行时调度器执行其他goroutine。
正如你指出的,如果从test
函数中移除对fmt.Println
的调用,test2()
的进度最终会停止(显然仅调用test()
并不足以让调度器继续执行。也许是因为它太快了,被内部的for循环内联,被阻塞等等)。
在这种情况下,忙碌循环对test()
的调用阻止了垃圾回收的停止-全局阶段,并阻塞了调度器。你可以通过使用GOGC=off
来验证这一点。
调度、CPU绑定任务、垃圾回收等的详细信息可能会因版本而异,但由于goroutine是协作调度的,忙碌循环总是一个错误。如果你确实需要一个长时间运行的、CPU绑定的循环,你可以通过偶尔插入对runtime.Gosched()
的调用来与调度器合作,让出给其他goroutine执行的机会。
英文:
An important distinction is that GOMAXPROCS=1
doesn't limit the runtime to 1 core, it limits the number of OS threads actively running user code to 1. All (gc derived) Go programs are multithreaded.
In most cases a busy loop will interfere with the scheduler and/or garbage collection, sometimes even if GOMAXPROCS > 1
. In your example though, the calls to the test()
and fmt.Println
functions are scheduling points, and those allow the runtime scheduler to execute other goroutines.
As you pointed out, if the call to fmt.Println
is removed from test
, the progress on test2()
eventually stops (apparently the call to test()
alone isn't sufficient to let the scheduler proceed. Maybe it's to fast, inlined, blocked by the internal for loop, so on).
In this case it is because the busy-loop calls to test()
are preventing the GC from doing it's stop-the-world phase of garbage collection, and blocking the scheduler. You can verify this by running the program with GOGC=off
.
The details of scheduling, cpu-bound tasks, garbage collection, etc. can change from release to release, but since goroutines are cooperatively scheduled, a busy-loop is always a mistake. If you have an actual need for a long running, cpu-bound loop, you can cooperate with the scheduler by ocasionally inserting a call to runtime.Gosched()
to yield to other goroutines.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论