douyueju2912 2018-04-02 19:22
浏览 84

Golang调度器之谜:Linux与Mac OS X

I've run into some mysterious behavior with the Go scheduler, and I'm very curious about what's going on. The gist is that runtime.Gosched() doesn't work as expected in Linux unless it is preceded by a log.Printf() call, but it works as expected in both cases on OS X. Here's a minimal setup that reproduces the behavior:

The main goroutine sleeps for 1000 periods of 1ms, and after each sleep pushes a dummy message onto another goroutine via a channel. The second goroutine listens for new messages, and every time it gets one it does 10ms of work. So without any runtime.Gosched() calls, the program will take 10 seconds to run.

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. I made sure to set runtime.GOMAXPROCS(1) in both cases.

Here's where it gets really strange: if I just add a logging statement before the the runtime.Gosched() calls, then suddenly the program runs in the expected 1 second on Ubuntu as well.

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

Additional details: I'm running go1.10 darwin/amd64, and compiling the linux binary with env GOOS=linux GOARCH=amd64 go build ...

I've tried a few simple variants:

  • Just making a log.Printf() call, without the Gosched()
  • Making two calls to Gosched()
  • Keeping the Gosched() call but replacing the log.Printf() call to a dummy function call

All of these are ~10x slower than calling log.Printf() and then Gosched().

Any insights would be appreciated! This example is of course very artificial, but the issue came up while writing a websocket broadcast server which led to significantly degraded performance.

EDIT: I got rid of the extraneous bits in my example to make things more transparent. I've discovered that without the print statement, the runtime.Gosched() calls are still getting run, it's just that they seem to be delayed by a fixed 5ms, leading to a total runtime of almost exactly 5seconds in the example below, when the program should finish instantaneously (and does on my Mac, or on Ubuntu with the print statement).

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条回答 默认 最新

  • dongsheng4679 2018-04-02 20:57
    关注

    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.


    On Ubuntu, I'm unable to reproduce your issue, one second, not ten seconds,

    Output:

    $ 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)
    }
    
    评论

报告相同问题?

悬赏问题

  • ¥15 python的qt5界面
  • ¥15 无线电能传输系统MATLAB仿真问题
  • ¥50 如何用脚本实现输入法的热键设置
  • ¥20 我想使用一些网络协议或者部分协议也行,主要想实现类似于traceroute的一定步长内的路由拓扑功能
  • ¥30 深度学习,前后端连接
  • ¥15 孟德尔随机化结果不一致
  • ¥15 apm2.8飞控罗盘bad health,加速度计校准失败
  • ¥15 求解O-S方程的特征值问题给出边界层布拉休斯平行流的中性曲线
  • ¥15 谁有desed数据集呀
  • ¥20 手写数字识别运行c仿真时,程序报错错误代码sim211-100