没有准备好读取频道时怎么办?

Nir*_*iel 5 channel go

让我们从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)
    }
}
}
Run Code Online (Sandbox Code Playgroud)

这有效.但是如果我不想在默认情况下打印或睡眠,但只是想保持循环呢?我试过这个:

    case <-boom:
        fmt.Println("BOOM!")
        return
    default: // Nothing here.
    }
}
}
Run Code Online (Sandbox Code Playgroud)

但它阻止了.

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

1)为什么阻止?

2)如何在没有阻止的情况下做任何事情?

Nic*_*ood 5

您的原始示例生成此

    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
BOOM!
Run Code Online (Sandbox Code Playgroud)

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

[process took too long]
Run Code Online (Sandbox Code Playgroud)

不同之处在于您在default案例中所做的工作.一个default案例总是准备好运行,因此select其中的默认语句永远不会阻塞.第二个示例围绕循环运行,连续选择一个准备运行的分支(case或default).您现在想知道为什么计时器永远不会触发.这是因为前程序没有先发制人.因此,因为下面的循环从不执行任何IO,所以时间滴答不会触发.

for {
    select {
        // whatever
        default:
    }
}
Run Code Online (Sandbox Code Playgroud)

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

最好的办法是恕我直言完全离开了默认的声明是这样.没有默认语句的select将阻塞,直到其中一个case语句准备就绪.如果你想做一些后台处理(你在默认语句中做的那样),那么就开始一个goroutine - 这就是go go!

事实上,我在select语句中看到了很多默认问题,我很想说它们从不使用它们.

  • 我偶尔使用默认值,所以我认为从不使用它们是不公平的.问题不是由select语句中的默认子句引起的,而是对goroutines如何工作的误解.即使没有select语句也会出现同样的问题,如stackoverflow中的问题所示.真正的问题是拥有一个繁忙的循环. (3认同)