diff --git a/ch8/ch8-07.md b/ch8/ch8-07.md index ab5da6b..44bcca9 100644 --- a/ch8/ch8-07.md +++ b/ch8/ch8-07.md @@ -1,3 +1,125 @@ ## 8.7. 基於select的多路複用 -TODO +下面的程序会进行火箭发射的倒计时。time.Tick函数返回一个channel,程序会周期性地像一个节拍器一样向这个channel发送事件。每一个事件的值是一个时间戳, but it is rarely as interesting as the fact of its delivery.(这句实在不会翻译。。) +```go +gopl.io/ch8/countdown1 +func main() { + fmt.Println("Commencing countdown.") + tick := time.Tick(1 * time.Second) + for countdown := 10; countdown > 0; countdown-- { + fmt.Println(countdown) + j<-tick + } + launch() +} +``` + +现在我们让这个程序支持在倒计时中,用户按下return键时直接中断发射流程。首先,我们启动一个goroutine,这个goroutine会尝试从标准输入中调入一个单独的byte并且,如果成功了,会向名为abort的channel发送一个值。 + +```go +gopl.io/ch8/countdown2 +abort := make(chan struct{}) +go func() { + os.Stdin.Read(make([]byte, 1)) // read a single byte + abort <- struct{}{} +}() +``` + +现在每一次计数循环的迭代都需要等待两个channel中的其中一个返回事件了:ticker channel当一切正常时(就像NASA jorgon的"nominal",译注:这梗估计我们是不懂了)或者异常时返回的abort事件。我们无法做到从每一个channel中接收信息,如果我们这么做的话,如果第一个channel中没有事件发过来那么程序就会立刻被阻塞,这样我们就无法收到第二个channel中发过来的事件。这时候我们需要多路复用(multiplex)这些操作了,为了能够多路复用,我们使用了select语句。 + +```go +select { +case <-ch1: + // ... +case x := <-ch2: + // ...use x... +case ch3 <- y: + // ... +default: + // ... +} +``` + +The general form of a select statement is shown above. Like a switch statement, it has a num- ber of cases and an optional default. Each case specifies a communication (a send or receive operation on some channel) and an associated block of statements. A receive expression may appear on its own, as in the first case, or within a short variable declaration, as in the second case; the second form lets you refer to the received value. +A select waits until a communication for some case is ready to proceed. It then performs that communication and executes the case’s associated statements; the other communications do not happen. A select with no cases, select{}, waits forever. +select会等待case中有能够执行的case时去执行。当条件满足时,select才会去通信并执行case之后的语句; +Let’s return to our rocket launch program. The time.After function immediately returns a channel, and starts a new goroutine that sends a single value on that channel after the speci- fied time. The select statement below waits until the first of two events arrives, either an abort event or the event indicating that 10 seconds have elapsed. If 10 seconds go by with no abort, the launch proceeds. + + +```go +func main() { + // ...create abort channel... + + fmt.Println("Commencing countdown. Press return to abort.") + select { + case <-time.After(10 * time.Second): + // Do nothing. + case <-abort: + fmt.Println("Launch aborted!") + return + } + launch() +``` + + +下面这个例子更微秒。ch这个channel的buffer大小是1,所以会交替的为空或为满,所以只有一个case可以进行下去,无论i是奇数或者偶数,它都会打印0 2 4 6 8。 + +```go +ch := make(chan int, 1) +for i := 0; i < 10; i++ { + select { + case x := <-ch: + fmt.Println(x) // "0" "2" "4" "6" "8" + case ch <- i: + } +} +``` + +如果多个case同时就绪时,select会随机地选择一个执行,这样来保证每一个channel都有平等的被select的机会。增加前一个例子的buffer大小会使其输出变得不确定,因为当buffer既不为满也不为空时,select语句的执行情况就像是抛硬币的行为一样是随机的。 +下面让我们的发射程序打印倒计时。这里的select语句会使每次循环迭代等待一秒来执行退出操作。 + +```go +gopl.io/ch8/countdown3 +func main() { + // ...create abort channel... + + fmt.Println("Commencing countdown. Press return to abort.") + tick := time.Tick(1 * time.Second) + for countdown := 10; countdown > 0; countdown-- { + fmt.Println(countdown) + select { + case <-tick: + // Do nothing. + case <-abort: + fmt.Println("Launch aborted!") + return + } + } + launch() +} +``` + +The time.Tick function behaves as if it creates a goroutine that calls time.Sleep in a loop, sending an event each time it wakes up. When the countdown function above returns, it stops receiving events from tick, but the ticker goroutine is still there, trying in vain to send on a channel from which no goroutine is receiving—a goroutine leak (§8.4.4). +Tick函数挺方便,但是只有当程序整个生命周期都需要这个时间时我们使用它才比较合适。否则的话,我们应该使用下面的这种模式: + +```go +ticker := time.NewTicker(1 * time.Second) +<-ticker.C // receive from the ticker's channel +ticker.Stop() // cause the ticker's goroutine to terminate +``` + +有时候我们希望能够从channel中发送或者接收值,并避免因为发送或者接收导致的阻塞,尤其是当channel没有准备好写或者读时。select语句就可以实现这样的功能。select会有一个default来设置当其它的操作都不能够马上被处理时程序需要执行哪些逻辑。 +下面的select语句会在abort channel中有值时,从其中接收值;无值时什么都不做。这是一个非阻塞的接收操作;反复地做这样的操作叫做“轮询channel”。 + +```go +select { +case <-abort: + fmt.Printf("Launch aborted!\n") + return +default: + // do nothing +} +``` +channel的零值是nil。也许会让你觉得比较奇怪,nil的channel有时候也是有一些用处的。因为对一个nil的channel发送和接收操作会永远阻塞,在select语句中操作nil的channel永远都不会被select到。 +This lets us use nil to enable or disable cases that cor- respond to features like handling timeouts or cancellation, responding to other input events, or emitting output. We’ll see an example in the next section. +练习8.8: 使用select来改造8.3节中的echo服务器,为其增加超时,这样服务器可以在客户端10秒中没有任何喊话时自动断开连接。