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

时间:2018-09-16 17:30:08

标签: for-loop go select 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.

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

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

1 个答案:

答案 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已关闭,它将一直运行直到其他程序停止该程序为止,否则它将在selectPlayground example)中再次阻塞

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

in the comments所述,在循环的每次迭代中对time.Sleep的调用都会阻塞一秒钟,因此通过关闭quit来停止程序的任何尝试都将延迟最多在goroutine检查quit并转义之前大约一秒钟。在程序停止之前,不可能完全完成此睡眠周期。

更多惯用的Go语言会在select语句中阻止从两个渠道接收消息:

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

解决方案

更改最少的解决方案

以最小的代码更改即可解决您的直接问题的解决方案是:

  • 通过向quit语句添加默认大小写,使select的读取不受阻塞。
  • quit读取成功后,
  • 确保goroutine实际上 返回:
    • 标记for循环并使用对break的标记调用;或
    • 是时候退出(首选)return函数中的
    • f1

根据您的情况,您可能会发现它更惯用。使用context.Context表示终止,并使用sync.WaitGroup等待goroutine完成后再从{{1}返回}。

main

Working example

(注意:我在您的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

相关文献