linux - Golang调度器之谜: Linux vs Mac OS X

标签 linux go concurrency scheduling

我遇到了 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/

相关文章:

linux - Linux 上的 Qt5 - Linguist 在哪里?

php - 如何使用 go 的 net/http 包提供 php 文件?

linux - 创建 UNIX "special character"文件

linux - Memcached 无法与 Webtatic 的 PHP5.6 一起使用

date - 如何使用不带数字偏移量的默认区域格式化日期

java - 在 Guava 事件线程安全上更新 JTable 的模式

java - 通过参数同步 Java 方法

c - 多种资源的细粒度锁定算法

linux - Arch Linux 没有 i586-elf-gcc 或 i586-elf-gcc

go - 如何创建一个go项目? [复制]