Golang调度程序之谜:Linux vs Mac OS X

时间:2018-04-02 19:22:21

标签: linux go concurrency scheduling

我在Go调度程序中遇到了一些神秘的行为,我对正在发生的事情非常好奇。要点是runtime.Gosched()在Linux中没有按预期工作,除非它之前是log.Printf()调用,但它在OS X上的两种情况下都能正常工作。这是一个重现行为的最小设置:

主要goroutine睡眠1000个1ms的时间段,并且在每次睡眠之后通过信道将虚拟消息推送到另一个goroutine上。第二个goroutine会收听新消息,每次获取一个消息时,它会完成10ms的工作。因此,如果没有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 ...

我尝试了一些简单的变体:

  • 只是在没有Gosched()
  • 的情况下进行log.Printf()调用
  • 两次致电Gosched()
  • 保持Gosched()调用,但将log.Printf()调用替换为虚拟函数调用

所有这些比调用log.Printf()和Gosched()慢〜10倍。

任何见解都将不胜感激!这个例子当然是非常人为的,但在编写websocket广播服务器时出现了问题,导致性能显着下降。

编辑:我摆脱了我的例子中的多余部分,使事情变得更加透明。我发现如果没有print语句,runtime.Gosched()调用仍在运行,只是它们似乎被延迟了5ms,在下面的例子中导致总运行时间几乎为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)
}

1 个答案:

答案 0 :(得分:0)

  

当我在第二个goroutine中添加定期的runtime.Gosched()调用时,   正如预期的那样,程序运行时在我的Mac上缩小到1秒。   但是,当我尝试在Ubuntu上运行相同的程序时,它仍然需要   10秒。

在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)
}