猿问

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

有人可以解释一下,为什么如果 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...

https://play.golang.org/p/MxKy2XqQlt8


慕妹3146593
浏览 97回答 1
1回答

MMMHUHU

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 mainimport (    "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和终止程序后立即返回。)修复睡眠阻塞问题要解决有关阻止立即退出的阻塞睡眠的其他问题,请将睡眠移动到块中的计时器select。for根据这个游乐场示例修改循环正是这样做的:for {    println("f1 is working...")    select {    case <-quit:        println("stopping f1")        return    case <-time.After(1 * time.Second):        // repeats loop    }}
随时随地看视频慕课网APP

相关分类

Go
我要回答