平时我们经常会用 for 和 select 语句来搭配使用来实现不断的通讯。
比如一个 goroutine 不断的从管道中发送字符,另一个不断的接收字符,当在管道关闭后,正常结束程序的执行。
如果我们不使用类似break, goto 等配合跳出到指定的标签,那么程序就不好控制正常结束了。
这里简要说明break,goto配合标签使用时的执行逻辑如下:
break是跳出到标签对应的当前指令,然后执行其下一条指令。
goto是跳往将要执行的指令。
goto + 标签
package main
import (
"fmt"
"time"
)
func main() {
intChan := make(chan int)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(time.Second)
intChan <- i
}
defer close(intChan)
}()
for {
select {
case i, ok := <-intChan:
if !ok {
fmt.Println("in break")
goto LOOP
}
fmt.Println(i)
case <-time.After(500 * time.Millisecond):
fmt.Println("time expired")
}
}
LOOP:
fmt.Println("end")
}
break + 标签
对于上述代码,如果我们想要使用break来改写,则如下
package main
import (
"fmt"
"time"
)
func main() {
intChan := make(chan int)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(time.Second)
intChan <- i
}
defer close(intChan)
}()
for {
select {
case i, ok := <-intChan:
if !ok {
fmt.Println("in break")
break LOOP
}
fmt.Println(i)
case <-time.After(500 * time.Millisecond):
fmt.Println("time expired")
}
}
LOOP:
fmt.Println("end")
}
但这样是不会通过编译的,显示如下:
# command-line-arguments
./breakloop.go:23:11: break label not defined: LOOP
./breakloop.go:30:1: label LOOP defined and not used
为什么呢?因为break这个不是goto,几乎可以到达程序的任何一个地方。它只能在其定义了后,才可以使用。
现在将其位置调整成如下,即可正常工作。
package main
import (
"fmt"
"time"
)
func main() {
intChan := make(chan int)
go func() {
for i := 0; i < 5; i++ {
time.Sleep(time.Second)
intChan <- i
}
defer close(intChan)
}()
LOOP:
for {
select {
case i, ok := <-intChan:
if !ok {
fmt.Println("in break")
break LOOP
}
fmt.Println(i)
case <-time.After(500 * time.Millisecond):
fmt.Println("time expired")
}
}
fmt.Println("end")
}
上面的LOOP标签,就指代了当前的for{}语句,当用break LOOP时,其会跳过当前
语句(for{}
),而开始执行下一条
语句(也即 fmt.Println("end")
)
如此,则满足我们需求了。如果break时不带标签,那么其只退出当前的 select语句,无法跳出 for,就永远不会结束了。
总之,break LOOP,需要出现在LOOP所标志的那条语句中。