"Select" goroutine 内部的 for 循环语句
"Select" statement inside of goroutine with for loop
谁能解释一下,为什么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.
但是如果"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...
f1 is working...
f1 is working...
f1 is working...
f1 is working...
没有 default
case 的 select
语句是 阻塞 直到 case
语句中的至少一个读或写可以被执行。因此,您的 select
将阻塞,直到可以从 quit
通道读取数据(如果通道关闭,则为值或零值)。语言规范提供了此行为的 concrete description,特别是:
If one or more of the communications [expressed in the case
statements] can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
其他代码问题
注意! break
适用于 select
语句
但是,即使您确实关闭了 quit
通道以发出关闭程序的信号,您的实施也可能不会产生预期的效果。对 break
的(未标记的)调用将 terminate execution of the inner-most for
, select
or switch
statement within the function. In this case, the select
statement will break and the for
loop will run through again. If quit
was closed, it will run forever until something else stops the program, otherwise it will block again in select
(Playground example)
关闭 quit
将(可能)不会立即停止程序
如前所述 ,对 time.Sleep
的调用在循环的每次迭代中阻塞一秒钟,因此任何通过关闭 quit
来停止程序的尝试都会延迟在 goroutine 检查 quit
并转义之前大约一秒。在程序停止之前,这个睡眠周期不太可能必须完全完成。
更多地道的 Go 会在 select
从两个通道接收的语句中阻塞:
quit
频道
time.After
– this call is an abstraction around a Timer
返回的通道休眠一段时间,然后将值写入提供的通道。
分辨率
修改最少的解决方案
通过对代码进行最少更改来解决您眼前的问题的解决方案是:
- 通过向
select
语句添加默认情况,使从 quit
的读取成为非阻塞。
- 当从
quit
读取成功时,确保 goroutine 实际上 returns:
- 标记
for
循环并使用标记调用 break
;或
return
从 f1
函数退出(首选)
根据您的情况,您可能会发现使用 context.Context
to signal termination, and to use a sync.WaitGroup
在从 main
.
返回之前等待 goroutine 完成更为惯用
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)
}
(注意:我在您的 main
方法中添加了一个额外的 time.Sleep
调用,以避免在调用 close
并终止程序后立即返回。)
解决睡眠阻塞问题
要解决有关阻止立即退出的阻塞睡眠的其他问题,请将睡眠移动到 select
块中的计时器。根据评论中的 playground example 修改 for
循环正是这样做的:
for {
println("f1 is working...")
select {
case <-quit:
println("stopping f1")
return
case <-time.After(1 * time.Second):
// repeats loop
}
}
相关文献
- Go-by-example: Non-blocking channel operations
- Dave Cheney's Channel axioms,特别是第四个公理“A 从一个关闭的通道returns 立即接收到零值。”
谁能解释一下,为什么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.
但是如果"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...
f1 is working...
f1 is working...
f1 is working...
f1 is working...
没有 default
case 的 select
语句是 阻塞 直到 case
语句中的至少一个读或写可以被执行。因此,您的 select
将阻塞,直到可以从 quit
通道读取数据(如果通道关闭,则为值或零值)。语言规范提供了此行为的 concrete description,特别是:
If one or more of the communications [expressed in the
case
statements] can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
其他代码问题
注意! break
适用于 select
语句
但是,即使您确实关闭了 quit
通道以发出关闭程序的信号,您的实施也可能不会产生预期的效果。对 break
的(未标记的)调用将 terminate execution of the inner-most for
, select
or switch
statement within the function. In this case, the select
statement will break and the for
loop will run through again. If quit
was closed, it will run forever until something else stops the program, otherwise it will block again in select
(Playground example)
关闭 quit
将(可能)不会立即停止程序
如前所述 time.Sleep
的调用在循环的每次迭代中阻塞一秒钟,因此任何通过关闭 quit
来停止程序的尝试都会延迟在 goroutine 检查 quit
并转义之前大约一秒。在程序停止之前,这个睡眠周期不太可能必须完全完成。
更多地道的 Go 会在 select
从两个通道接收的语句中阻塞:
quit
频道time.After
– this call is an abstraction around aTimer
返回的通道休眠一段时间,然后将值写入提供的通道。
分辨率
修改最少的解决方案
通过对代码进行最少更改来解决您眼前的问题的解决方案是:
- 通过向
select
语句添加默认情况,使从quit
的读取成为非阻塞。 - 当从
quit
读取成功时,确保 goroutine 实际上 returns:- 标记
for
循环并使用标记调用break
;或 return
从f1
函数退出(首选)
- 标记
根据您的情况,您可能会发现使用 context.Context
to signal termination, and to use a sync.WaitGroup
在从 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)
}
(注意:我在您的 main
方法中添加了一个额外的 time.Sleep
调用,以避免在调用 close
并终止程序后立即返回。)
解决睡眠阻塞问题
要解决有关阻止立即退出的阻塞睡眠的其他问题,请将睡眠移动到 select
块中的计时器。根据评论中的 playground example 修改 for
循环正是这样做的:
for {
println("f1 is working...")
select {
case <-quit:
println("stopping f1")
return
case <-time.After(1 * time.Second):
// repeats loop
}
}
相关文献
- Go-by-example: Non-blocking channel operations
- Dave Cheney's Channel axioms,特别是第四个公理“A 从一个关闭的通道returns 立即接收到零值。”