有人可以解释一下,为什么goroutine在循环内有无穷无尽的for
循环和select
时,循环中的代码只能运行一次?
package main
import (
"time"
)
func f1(quit chan bool){
go func() {
for {
println("f1 is working...")
time.Sleep(1 * time.Second)
select{
case <-quit:
println("stopping f1")
break
}
}
}()
}
func main() {
quit := make(chan bool)
f1(quit)
time.Sleep(4 * time.Second)
}
输出:
f1 is working...
Program exited.
但是如果“选择”被注释掉了:
package main
import (
"time"
)
func f1(quit chan bool){
go func() {
for {
println("f1 is working...")
time.Sleep(1 * time.Second)
//select{
//case <-quit:
//println("stopping f1")
//break
//}
}
}()
}
func main() {
quit := make(chan bool)
f1(quit)
time.Sleep(4 * time.Second)
}
输出:
f1 is working...
f1 is working...
f1 is working...
f1 is working...
f1 is working...
答案 0 :(得分:5)
没有select
情况的default
语句将被阻塞,直到可以执行至少case
个语句之一的读取或写入操作。因此,您的select
将阻塞,直到可以从quit
通道进行读取(如果关闭了通道,则该值可以为零,也可以为零)。语言规范提供了此行为的concrete description,具体是:
如果[
case
语句中表示的一种或多种通信可以进行,则可以通过统一的伪随机选择来选择可以进行的单个通信。否则,如果存在默认情况,则选择该情况。如果没有默认情况,则“ select”语句将阻塞,直到可以进行至少一种通信为止。
break
适用于select
语句但是,即使您确实关闭了quit
通道以表示程序已关闭,您的实现也可能不会达到预期的效果。对break
的(未标记)调用将在函数内terminate execution of the inner-most for
, select
or switch
statement。在这种情况下,select
语句将中断,for
循环将再次运行。如果quit
已关闭,它将一直运行直到其他程序停止该程序为止,否则它将在select
(Playground example)中再次阻塞
quit
(可能)不会立即停止程序如in the comments所述,在循环的每次迭代中对time.Sleep
的调用都会阻塞一秒钟,因此通过关闭quit
来停止程序的任何尝试都将延迟最多在goroutine检查quit
并转义之前大约一秒钟。在程序停止之前,不可能完全完成此睡眠周期。
更多惯用的Go语言会在select
语句中阻止从两个渠道接收消息:
quit
频道time.After
返回的通道–此调用是围绕Timer
的抽象,它休眠一段时间,然后将值写入所提供的通道。以最小的代码更改即可解决您的直接问题的解决方案是:
quit
语句添加默认大小写,使select
的读取不受阻塞。quit
读取成功后,for
循环并使用对break
的标记调用;或return
函数中的f1
根据您的情况,您可能会发现它更惯用。使用context.Context
表示终止,并使用sync.WaitGroup
等待goroutine完成后再从{{1}返回}。
main
(注意:我在您的package main
import (
"fmt"
"time"
)
func f1(quit chan bool) {
go func() {
for {
println("f1 is working...")
time.Sleep(1 * time.Second)
select {
case <-quit:
fmt.Println("stopping")
return
default:
}
}
}()
}
func main() {
quit := make(chan bool)
f1(quit)
time.Sleep(4 * time.Second)
close(quit)
time.Sleep(4 * time.Second)
}
方法中添加了一个额外的time.Sleep
调用,以避免在调用main
并终止程序后立即返回。)
要解决与阻止睡眠阻止立即退出有关的其他问题,请将睡眠移至close
块中的计时器。根据评论中的playground example修改select
循环的过程完全是这样:
for