【问题标题】:Why my goroutine stopped after a few moments running?为什么我的 goroutine 在运行了几分钟后就停止了?
【发布时间】:2020-01-04 10:43:45
【问题描述】:
package main

import (
    "fmt"
    "runtime"
)

func main() {
    runtime.GOMAXPROCS(1)
    go spinner()
    const n = 45
    fibN := fib(n)
    fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}

func spinner() {
    for {
        for _ = range `-\|/` {
        }
    }
}

func fib(x int) int {
    fmt.Println(x)
    if x < 2 {
        return x
    }
    return fib(x-1) + fib(x-2)
}

几秒钟后,它停止在fib 中打印x。我故意使用 1 个处理器来了解它为什么停止。调度器是否卡住并且不再重新调度运行fib 的goroutine?

【问题讨论】:

  • @icza 我认为这不是“当主 goroutine 结束时,您的应用程序也会结束”的问题,我使用运行时跟踪器运行代码,似乎问题是带有 fmt.Println 的 golang 调度程序在几次致电fib 之后,永远不要重新安排spinner,这可能是到期的(我很可能是错的),但go 的调度程序部分地抢占式并不完全。此外,fib 算法具有指数复杂度,因此调用 fib 不会那么早返回。
  • @GauravDhiman 是的,对。没有显式同步的 Goroutine 调度是“无法控制的”,spinner() 有一个繁忙的循环,goroutine 调度程序永远不会切换回 main goroutine。
  • @gaurav-dhiman,在 Go ≤ 1.13 中,它根本不是抢占式的。 1.14应该是第一个发布的时候“差不多”抢占式了,紧循环的问题应该解决了。
  • 请注意,虽然可能会出现抢占循环的改进,但繁忙的循环几乎总是一个编程错误,应该避免。以 100% 的速度旋转 CPU 而不是使用阻塞机制很少是您想要做的。
  • @JimB:除了有时您确实需要这样做(并且 100% 的 CPU 使用率不是问题)。我希望他们给我们留下这个选择。

标签: go concurrency goroutine


【解决方案1】:

调度器是否卡住了,不再重新调度运行 fib 的 goroutine?

简短回答:是的。

更长的答案:您不能保证这会发生,即使只有一个 CPU。该系统可以自己进行偶尔的重新安排。只是您正在测试的实现没有。

作为JimB notes in a comment,您在spinner 中拥有的那种硬自旋循环是......充其量是不友好的。它应该包含一些释放计算资源的方法,例如使用time.Aftertime.NewTicker 旋转,或者至少调用runtime.Gosched()。我想你原来的spinner 是这样的,只是没有time.NewTicker

func spinner() {
    t := time.NewTicker(50 * time.Millisecond)
    for {
        for _, c := range `-\|/` {
            fmt.Print(string(c), "\b")
            <-t.C
        }
    }
}

有了自动收报机,微调器旋转得很快,但程序完成(尽管没有缓存fib)。当然,更聪明的fib 会更有帮助:

var fibcache = map[int]int64{}

func fib(x int) int64 {
    if x < 2 {
        return int64(x)
    }
    r, ok := fibcache[x]
    if !ok {
        r = fib(x-1) + fib(x-2)
        fibcache[x] = r
    }
    return r
}

(我将返回类型更改为int64,我希望这是显而易见的原因。)现在我们可以在 0.00 秒(四舍五入)内找到Fibonacci(90) = 2880067194370816120

【讨论】:

  • 所有无限循环和幼稚递归 fib 的使用都是故意的,因为我试图理解它为什么会卡住,为此使用更快的解决方案(或使用ticker),问题没有t 发生。现在我明白了,当调度程序运行spinner goroutine 时,它​​再也不会重新调度fib,因为它陷入了循环。无论如何,感谢您的 sn-p。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-29
  • 2022-01-15
相关资源
最近更新 更多