为了账号安全,请及时绑定邮箱和手机立即绑定

带有for循环的goroutine内部的“选择”语句

带有for循环的goroutine内部的“选择”语句

Go
慕妹3146593 2023-04-04 15:25:37
有人可以解释一下,为什么如果 goroutine 有无限for循环并且select在循环内部,循环中的代码只运行一次?package mainimport (    "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 mainimport (    "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...https://play.golang.org/p/MxKy2XqQlt8
查看完整描述

1 回答

?
MMMHUHU

TA贡献1834条经验 获得超8个赞

select没有 case 的语句是default阻塞case,直到可以执行至少一个语句中的读取或写入。因此,您select将阻塞,直到可以从通道读取quit(如果通道关闭,则为值或零值)。语言规范提供了对此行为的具体描述,特别是:

如果一个或多个通信[在陈述中表达case]可以继续,则通过统一的伪随机选择选择一个可以继续的通信。否则,如果存在默认情况,则选择该情况。如果没有默认情况,则“select”语句将阻塞,直到至少有一个通信可以继续进行。

其他代码问题

警告!break适用于select声明

但是,即使您确实关闭了quit通道以发出关闭程序的信号,您的实施也可能不会产生预期的效果。对 的(未标记的)调用break将终止函数内最内层的fororselect语句switch的执行。在这种情况下,select语句将中断,for循环将再次运行。如果quit被关闭,它将永远运行直到其他东西停止程序,否则它将再次阻塞select

关闭quit将(可能)不会立即停止程序

正如time.Sleep所指出的,在循环的每次迭代中调用都会阻塞一秒钟,因此任何通过关闭来停止程序的尝试都会在 goroutine 检查和退出quit之前延迟大约一秒钟。quit在程序停止之前,这个睡眠周期不太可能必须完全完成。

更惯用的 Go 会在select从两个渠道接收的语句中阻塞:

  • 频道quit_

  • 返回的通道time.After– 此调用是围绕 a 的抽象Timer,它会休眠一段时间,然后将值写入提供的通道。

解决

更改最少的解决方案

通过对代码进行最少更改来解决您的直接问题的解决方案是:

  • 通过向语句添加默认情况,使读取成为quit非阻塞的select

  • 确保 goroutine在读取成功时实际quit返回:

    • 标记for循环并使用标记调用break; 或者

    • returnf1在需要退出时从函数中获取(首选)

根据您的情况,您可能会发现 Go 更惯用地使用 a 来context.Context发出终止信号,并使用 async.WaitGroup等待 goroutine 在从 返回之前完成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和终止程序后立即返回。)

修复睡眠阻塞问题

要解决有关阻止立即退出的阻塞睡眠的其他问题,请将睡眠移动到块中的计时器selectfor根据这个游乐场示例修改循环正是这样做的:

for {

    println("f1 is working...")


    select {

    case <-quit:

        println("stopping f1")

        return

    case <-time.After(1 * time.Second):

        // repeats loop

    }

}


查看完整回答
反对 回复 2023-04-04
  • 1 回答
  • 0 关注
  • 96 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信