3
func main() {
    c := make(chan os.Signal, 1)
    signal.Notify(c)

    ticker := time.NewTicker(time.Second)
    stop := make(chan bool)

    go func() {
        defer func() { stop <- true }()
        for {
            select {
            case <-ticker.C:
                fmt.Println("Tick")
            case <-stop:
                fmt.Println("Goroutine closing")
                return
            }
        }
    }()

    <-c
    ticker.Stop()

    stop <- true

    <-stop
    fmt.Println("Application stopped")
}

No matter how many times I run the code above, I got the same result. That is, "Goroutine closing" is always printed before "Application stopped" after I press Ctrl+C.

I think, theoretically, there is a chance that "Goroutine closing" won't be printed at all. Am I right? Unfortunately, I never get this theoretical result.

BTW: I know reading and writing a channel in one routine should be avoided. Ignore that temporarily.

Leon
  • 2,926
  • 1
  • 25
  • 34
Scala
  • 31
  • 2

1 Answers1

3

In your case, Goroutine closing will always be executed and it will always be printed before Application stopped, because your stop channel is not buffered. This means that the sending will block until the result is received.

In your code, the stop <- true in your main will block until the goroutine has received the value, causing the channel to be empty again. Then the <-stop in your main will block until another value is sent to the channel, which happens when your goroutine returns after printing Goroutine closing.

If you would initialize your channel in a buffered fashion

stop := make(chan bool, 1)

then Goroutine closing might not be executed. To see this, you can add a time.Sleep right after printing Tick, as this makes this case more likely (it will occur everytime you press Ctrl+C during the sleep).

Using a sync.WaitGroup to wait for goroutines to finish is a good alternative, especially if you have to wait for more than one goroutine. You can also use a context.Context to stop goroutines. Reworking your code to use these two methods could look something like this:

func main() {
    c := make(chan os.Signal, 1)
    signal.Notify(c)

    ticker := time.NewTicker(time.Second)
    ctx, cancel := context.WithCancel(context.Background())
    var wg sync.WaitGroup

    wg.Add(1)
    go func() {
        defer func() { wg.Done() }()
        for {
            select {
            case <-ctx.Done():
                fmt.Println("Goroutine closing")
                return
            case <-ticker.C:
                fmt.Println("Tick")
                time.Sleep(time.Second)
            }
        }
    }()

    <-c
    ticker.Stop()

    cancel()

    wg.Wait()

    fmt.Println("Application stopped")
}
Leon
  • 2,926
  • 1
  • 25
  • 34