关于 go:select 的奇怪行为(不允许其他 goroutine 运行)


Strange behaviour of select (does not allow other goroutines to run)

我正在尝试使用 https://github.com/klkblake/Go-SDL 在 Go 中编写 SDL 应用程序。
我创建了计时器来调用它的绘图函数:

1
render_timer := time.NewTicker(time.Second / 60)

事件循环中的某处:

1
2
3
4
5
6
7
8
9
10
11
12
for running == true {
    [...]
    [process sdl events]
    [...]
    select {
    case <-render_timer.C:
        call_my_draw_function()
    default:
        some_default_actions()
    }
    [...]
}

如果我在编译此代码后运行程序,屏幕上不会绘制任何内容。但如果我只放置:

1
fmt.Println("default")

在 select 的默认分支中——代码开始按我的意愿工作(在窗口中绘制一些东西);如果我再次删除 println 它就不要画任何东西。
我究竟做错了什么?为什么会有这样的 select 行为?

嗯...最简单的测试用例是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"time"
)

func main() {

    rt := time.NewTicker(time.Second / 60)
    for {
        select {
        case <-rt.C:
            fmt.Println("time")
        default:
        }
    time.Sleep(1) // without this line 'case <-rt.C' is never executed
    }
}

至于你的例子,你的循环是一个繁忙的循环,不断地碰到 default: 的情况。 go 中的调度程序是协作的,并且由于您处于忙碌的循环中,因此运行 Ticker 的 go 例程将永远不会被安排运行,因此永远不会在通道上发送任何内容。即使您的 default: 案例不为空,但进行纯计算 - 永远不会进行任何调用 schudler.

然而,当你做一些以某种形式调用 go 调度器的事情时,例如做 I/O ,调度器会给 Ticker 一个运行的机会。

您可以导入运行时包并执行

1
2
default:
    runtime.Gosched()

让调度程序运行,这不会饿死 Ticker goroutine。

我不确定这会如何导致您在运行 SDL 时遇到问题,因为这很可能涉及 I/O 或其他触发调度程序的事情


请参阅 golang:除非我添加了 fmt.Print()

,否则带有 select 的 goroute 不会停止

长话短说,由于默认情况,并且因为您的 GOMAXPROCS 可能设置为 1(默认值),它从不安排 goroutine 完成其工作。有很多选项可以解决这个问题,具体取决于您的需要(默认睡眠、选择而不是默认的 time.After() 通道、对 runtime.Gosched() 的调用等)。

添加 fmt.Print 使其工作,因为 - 我的猜测,不确定 - 它涉及 io 并在内部导致 Gosched() (或者类似 Phlip 在相关问题中的答案,我刚刚阅读了它)。