douhai5835
2018-09-16 17:30 阅读 61
已采纳

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

Could somebody please explain, why if goroutine has endless for loop and select inside of the loop, a code in the loop is run only one time?

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)
}

Output:

f1 is working...
Program exited.

But if "select" is commented out:

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)
}

Output:

f1 is working...
f1 is working...
f1 is working...
f1 is working...
f1 is working...

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

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享

1条回答 默认 最新

  • 已采纳
    dongxie559554 dongxie559554 2018-09-16 18:59

    A select statement without a default case is blocking until a read or write in at least one of the case statements can be executed. Accordingly, your select will block until a read from the quit channel is possible (either of a value or the zero value if the channel is closed). The language spec provides a concrete description of this behavior, specifically:

    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.

    Other code issues

    Caution! break applies to the select statement

    However, even if you did close the quit channel to signal shutdown of your program, your implementation would likely not have the desired effect. A (unlabelled) call to break will 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)

    Closing quit will (probably) not immediately stop the program

    As noted in the comments, the call to time.Sleep blocks for a second on each iteration of the loop, so any attempt to stop the program by closing quit will be delayed for up to approximately a second before the goroutine checks quit and escapes. It is unlikely this sleep period must complete in its entirety before the program stops.

    More idiomatic Go would block in the select statement on receiving from two channels:

    • The quit channel
    • The channel returned by time.After – this call is an abstraction around a Timer which sleeps for a period of time and then writes a value to the provided channel.

    Resolution

    Solution with minimal changes

    A resolution to fix your immediate issue with minimal changes to your code would be:

    • Make the read from quit non-blocking by adding a default case to the select statement.
    • Ensuring the goroutine actually returns when the read from quit succeeds:
      • Label the for loop and use a labelled call to break; or
      • return from the f1 function when it is time to quit (preferred)

    Depending on your circumstances, you may find it more idiomatic Go to use a context.Context to signal termination, and to use a sync.WaitGroup to wait for the goroutine to finish before returning from 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)
    }
    

    Working example

    (Note: I have added an additional time.Sleep call in your main method to avoid this returning immediately after the call to close and terminating the program.)

    Fixing the sleep blocking issue

    To fix the additional issue regarding the blocking sleep preventing immediate quit, move the sleep to a timer in the select block. Modifying your for loop as per this playground example from the comments does exactly this:

    for {
        println("f1 is working...")
    
        select {
        case <-quit:
            println("stopping f1")
            return
        case <-time.After(1 * time.Second):
            // repeats loop
        }
    }
    

    Related literature

    点赞 评论 复制链接分享

相关推荐