Kurt Peek
Kurt Peek

Reputation: 57381

Go ticker example doesn't select the 'done' case?

I've adapted this example, https://gobyexample.com/tickers, to the following script:

package main

import (
    "fmt"
    "time"
)

func main() {
    ticker := time.NewTicker(500 * time.Millisecond)
    done := make(chan bool)

    go func() {
        for {
            select {
            case <-done:
                fmt.Println("Received 'done'")
                return
            case t := <-ticker.C:
                fmt.Println("Tick at", t)
            }
        }
    }()

    time.Sleep(1600 * time.Millisecond)
    // ticker.Stop()
    done <- true
    // fmt.Println("Ticker stopped.")
}

Two differences with the referenced example are that I've commented out the ticker.Stop() line and added a fmt.Println("Received 'done'") line in the case <-done block. If I run this, I observe the following output:

> go run tickers.go
Tick at 2019-10-06 15:25:50.576798 -0700 PDT m=+0.504913907
Tick at 2019-10-06 15:25:51.074993 -0700 PDT m=+1.003102855
Tick at 2019-10-06 15:25:51.576418 -0700 PDT m=+1.504521538

My question: why does it not print Received 'done' to the terminal?

Strangely, if I comment in the Ticker stopped Println statement, I do see Received 'done' as well:

> go run tickers.go
Tick at 2019-10-06 15:27:30.735163 -0700 PDT m=+0.504666656
Tick at 2019-10-06 15:27:31.234076 -0700 PDT m=+1.003573649
Tick at 2019-10-06 15:27:31.735342 -0700 PDT m=+1.504833296
Ticker stopped.
Received 'done'

As a I recall, the code within a Goroutine can be assuming to run synchronously, so I'm perplexed that I don't see the effect of the Println statement in the former case since it happens before the Goroutine returns. Can someone explain this?

Upvotes: 0

Views: 2003

Answers (2)

torek
torek

Reputation: 487725

... why does it not print Received 'done' to the terminal?

It does—or rather, it tries.

A Go program exits when the main goroutine (which called main of package main) returns (or sooner in various cases not occurring here). Your main returns after calling time.Sleep() and then sending true on done.

Meanwhile, the goroutine that's in the for loop awakens when the true value arrives on the done channel. This happens after the main goroutine has sent it, after which the main goroutine is in the process of exiting.

If, in this process of exiting, the main goroutine takes long enough, the anonymous goroutine will have time to print Received 'done'. If, in this process of exiting, the main goroutine is speedy enough, the anonymous goroutine never finishes, or maybe never even starts, printing anything, and you see nothing. (The actual output is accomplished by a single underlying system call so you either get all of it, or none of it.)

You can make sure the goroutine you spun off finishes before having your main one exit by any number of mechanisms, but the simplest is probably to use sync.WaitGroup since it's kind of designed for that. Create a waitgroup, set its counter to 1 (add 1 to its initial zero), and then call the Done function on the way out of the anonymous goroutine. Have the main goroutine wait for it.

See Go Playground example.

Upvotes: 1

p1gd0g
p1gd0g

Reputation: 711

When done <- true happened, the main function returned directly.

You could add another time.Sleep() to see what happened.

    time.Sleep(1600 * time.Millisecond)
    // ticker.Stop()
    done <- true
    // fmt.Println("Ticker stopped.")
    time.Sleep(1600 * time.Millisecond)

Upvotes: 2

Related Questions