5

让我们以 GoTour 中的这个示例为例,因为它说明了我仅在有事件时才处理 SDL 事件的问题。

package main

import (
"fmt"
"time"
)

func main() {
tick := time.Tick(1e8)
boom := time.After(5e8)
for {
    select {
    case <-tick:
        fmt.Println("tick.")
    case <-boom:
        fmt.Println("BOOM!")
        return
    default:
        fmt.Println("    .")
        time.Sleep(5e7)
    }
}
}

这行得通。但是如果我不想在默认情况下打印或睡眠,而只想继续循环呢?我试过这个:

    case <-boom:
        fmt.Println("BOOM!")
        return
    default: // Nothing here.
    }
}
}

但它会阻塞。

我在这里和那里看到过关于goroutines调度的一句话,但我没有理解它们。所以我想我有两个问题:

1)为什么会阻塞?

2)如何让它不做任何阻塞?

4

1 回答 1

6

您的原始示例产生了这个

    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
BOOM!

你的第二个例子产生了这个

[process took too long]

不同之处在于您在default案件中所做的事情。案例default始终准备好运行,因此select其中包含默认语句的案例永远不会阻塞。第二个示例围绕循环运行,不断选择准备运行的分支之一(案例或默认)。您现在想知道为什么计时器从不触发。那是因为 goroutines 不是预先安排好的。所以因为下面的循环从不做任何 IO,所以时间滴答永远不会触发。

for {
    select {
        // whatever
        default:
    }
}

有很多方法可以解决这个问题。首先,您可以像在第一个示例中那样放入一些 IO。或者你可以放一个runtime.Gosched()。或者你可以允许 go 运行时使用多个线程和runtime.GOMAXPROCS(2)所有这些都可以工作。

恕我直言,最好的方法是完全像这样省略默认语句。没有默认语句的 select 将阻塞,直到其中一个 case 语句准备好。如果你想做一些后台处理(你在默认语句中做的)然后启动一个 goroutine - 这就是 go 方式!

事实上,我已经在 select 语句中看到了很多关于 default 的问题,以至于我很想说永远不要使用它们。

于 2013-01-31T20:29:44.530 回答