我遇到了 Go 调度程序的一些神秘行为,我很好奇发生了什么。要点是 runtime.Gosched()
在 Linux 中不能按预期工作,除非它之前有 log.Printf()
调用,但它在 Linux 中按预期工作OS X 上的两种情况。这是重现该行为的最小设置:
主协程休眠 1000 次,每次 1 毫秒,每次休眠后通过 channel 将虚拟消息推送到另一个协程。第二个 goroutine 监听新消息,每次它收到一条消息,它就做 10 毫秒的工作。因此,如果没有任何 runtime.Gosched()
调用,程序将需要 10 秒才能运行。
当我在第二个 goroutine 中添加周期性的 runtime.Gosched()
调用时,正如预期的那样,我的 Mac 上的程序运行时间缩短到 1 秒。但是,当我尝试在 Ubuntu 上运行相同的程序时,它仍然需要 10 秒。我确保在这两种情况下都设置了 runtime.GOMAXPROCS(1)
。
这就是真正奇怪的地方:如果我只是在 runtime.Gosched()
调用之前添加一个日志语句,那么程序突然也会在 Ubuntu 上以预期的 1 秒运行。
package main
import (
"time"
"log"
"runtime"
)
func doWork(c chan int) {
for {
<-c
// This outer loop will take ~10ms.
for j := 0; j < 100 ; j++ {
// The following block of CPU work takes ~100 microseconds
for i := 0; i < 300000; i++ {
_ = i * 17
}
// Somehow this print statement saves the day in Ubuntu
log.Printf("donkey")
runtime.Gosched()
}
}
}
func main() {
runtime.GOMAXPROCS(1)
c := make(chan int, 1000)
go doWork(c)
start := time.Now().UnixNano()
for i := 0; i < 1000; i++ {
time.Sleep(1 * time.Millisecond)
// Queue up 10ms of work in the other goroutine, which will backlog
// this goroutine without runtime.Gosched() calls.
c <- 0
}
// Whole program should take about 1 second to run if the Gosched() calls
// work, otherwise 10 seconds.
log.Printf("Finished in %f seconds.", float64(time.Now().UnixNano() - start) / 1e9)
}
其他详细信息:我正在运行 go1.10 darwin/amd64,并使用以下命令编译 linux 二进制文件
env GOOS=linux GOARCH=amd64 go build ...
我尝试了一些简单的变体:
- 只调用 log.Printf(),没有 Gosched()
- 两次调用 Gosched()
- 保留 Gosched() 调用但将 log.Printf() 调用替换为虚拟函数调用
所有这些都比调用 log.Printf() 然后 Gosched() 慢 10 倍。
如有任何见解,我们将不胜感激!这个例子当然是非常人为的,但是在编写 websocket 广播服务器时出现了问题,导致性能显着下降。
编辑:我去掉了示例中无关的部分,使事情更加透明。我发现如果没有 print 语句,runtime.Gosched()
调用仍在运行,只是它们似乎延迟了固定的 5 毫秒,导致总运行时间几乎正好在下面的示例中,程序应立即完成 5 秒(在我的 Mac 上,或在 Ubuntu 上使用 print 语句)。
package main
import (
"log"
"runtime"
"time"
)
func doWork() {
for {
// This print call makes the code run 20x faster
log.Printf("donkey")
// Without this line, the program never terminates (as expected). With this line
// and the print call above it, the program takes <300ms as expected, dominated by
// the sleep calls in the main goroutine. But without the print statement, it
// takes almost exactly 5 seconds.
runtime.Gosched()
}
}
func main() {
runtime.GOMAXPROCS(1)
go doWork()
start := time.Now().UnixNano()
for i := 0; i < 1000; i++ {
time.Sleep(10 * time.Microsecond)
runtime.Gosched()
}
log.Printf("Finished in %f seconds.", float64(time.Now().UnixNano() - start) / 1e9)
}
最佳答案
When I add periodic runtime.Gosched() calls in the second goroutine, as expected the program runtime shrinks down to 1 second on my Mac. However, when I try running the same program on Ubuntu, it still takes 10 seconds.
在 Ubuntu 上,我无法重现您的问题,一秒钟,而不是十秒钟,
输出:
$ uname -srvm
Linux 4.13.0-37-generic #42-Ubuntu SMP Wed Mar 7 14:13:23 UTC 2018 x86_64
$ go version
go version devel +f1deee0e8c Mon Apr 2 20:18:14 2018 +0000 linux/amd64
$ go build rampatowl.go && time ./rampatowl
2018/04/02 16:52:04 Finished in 1.122870 seconds.
real 0m1.128s
user 0m1.116s
sys 0m0.012s
$
rampatowl.go
:
package main
import (
"log"
"runtime"
"time"
)
func doWork(c chan int) {
for {
<-c
// This outer loop will take ~10ms.
for j := 0; j < 100; j++ {
// The following block of CPU work takes ~100 microseconds
for i := 0; i < 300000; i++ {
_ = i * 17
}
// Somehow this print statement saves the day in Ubuntu
//log.Printf("donkey")
runtime.Gosched()
}
}
}
func main() {
runtime.GOMAXPROCS(1)
c := make(chan int, 1000)
go doWork(c)
start := time.Now().UnixNano()
for i := 0; i < 1000; i++ {
time.Sleep(1 * time.Millisecond)
// Queue up 10ms of work in the other goroutine, which will backlog
// this goroutine without runtime.Gosched() calls.
c <- 0
}
// Whole program should take about 1 second to run if the Gosched() calls
// work, otherwise 10 seconds.
log.Printf("Finished in %f seconds.", float64(time.Now().UnixNano()-start)/1e9)
}
关于linux - Golang调度器之谜: Linux vs Mac OS X,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49617451/