带有for循环的goroutine中的“ Select”语句

Dim*_*maf 1 select for-loop go 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)
}
Run Code Online (Sandbox Code Playgroud)

输出:

f1 is working...
Program exited.
Run Code Online (Sandbox Code Playgroud)

但是,如果“选择”被注释掉:

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)
}
Run Code Online (Sandbox Code Playgroud)

输出:

f1 is working...
f1 is working...
f1 is working...
f1 is working...
f1 is working...
Run Code Online (Sandbox Code Playgroud)

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

Cos*_*age 5

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

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

其他代码问题

警告!break适用于该select陈述

但是,即使您确实关闭了quit通道以发出程序关闭信号,您的实现也可能不会达到预期的效果。A(未标记的)呼叫break终止最内部的执行forselectswitch语句的功能之内。在这种情况下,该select语句将中断并且for循环将再次运行。如果quit已关闭,它将一直运行,直到其他程序停止该程序为止,否则它将再次阻塞select(在Playground示例中

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

如注释中所述,在time.Sleep循环的每次迭代中,对块的调用将持续一秒钟,因此,通过quitgoroutine检查quit并转义之前,通过关闭停止程序的任何尝试都将延迟大约一秒钟。在程序停止之前,此睡眠期不可能完整地完成。

更多惯用的Go会阻止select从两个渠道接收的声明:

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

解析度

最小的变化解决方案

解决方案以最小的代码更改来解决您的即时问题,将是:

  • quit通过在select语句中添加默认大小写,从非阻塞状态进行读取。
  • 确保goroutine 实际上在读取quit成功时返回:
    • 标记for循环,并使用标记的调用break;要么
    • returnf1当是时候退出(优选的)函数

根据您的情况,您可能会发现它更惯用Go来使用context.Context信号终止,并使用Go sync.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)
}
Run Code Online (Sandbox Code Playgroud)

工作实例

(注意:我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
    }
}
Run Code Online (Sandbox Code Playgroud)

相关文献