首先等待时间.AfterFunc然后开始时间.NewTicker

I am trying to set up a service routine that runs every hour on the hour. It seems to me that either of those two are easy. To run my routine on the hour I can use time.AfterFunc(), first calculating the remaining time until the top of the hour. And to run my routine every hour I can use time.NewTicker().

However, I'm struggling to figure out how to start the NewTicker only after the function passed to AfterFunc() has fired.

My main() function looks something like this:

func main() {
    fmt.Println("starting up")

    // Here I'm setting up all kinds of HTTP listeners and gRPC listeners, none
    // of which is important, save to mention that the app has more happening
    // than just this service routine.

    // Calculate duration until next hour and call time.AfterFunc()
    // For the purposes of this exercise I'm just using 5 seconds so as not to
    // have to wait increments of hours to see the results
    time.AfterFunc(time.Second * 5, func() {
        fmt.Println("AfterFunc")
    })

    // Set up a ticker to run every hour. Again, for the purposes of this
    // exercise I'm ticking every 2 seconds just to see some results
    t := time.NewTicker(time.Second * 2)
    defer t.Stop()
    go func() {
        for now := range t.C {
            fmt.Println("Ticker")
        }
    }()

    // Block until termination signal is received
    osSignals := make(chan os.Signal, 1)
    signal.Notify(osSignals, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
    <-osSignals

    fmt.Println("exiting gracefully")
}

Of course time.AfterFunc() is blocking and the payload of my Ticker is deliberately put in a go routine so it also won't be blocking. This is so that my HTTP and gRPC listeners can continue to listen but also to allow the block of code at the end of main() to exit gracefully upon a termination signal from the OS. But the obvious downside now is that the Ticker kicks off pretty much immediately and fires twice (2 second intervals) before the function passed to AfterFunc() fires. The output looks like this:

Ticker
Ticker
AfterFunc
Ticker
Ticker
Ticker
etc.

What I wanted of course is:

AfterFunc
Ticker
Ticker
Ticker
Ticker
Ticker
etc.

The following also doesn't work and I'm not exactly sure why. It prints AfterFunc but the Ticker never fires.

time.AfterFunc(time.Second * 5, func() {
        fmt.Println("AfterFunc")

        t := time.NewTicker(time.Second * 2)
        defer t.Stop()
        go func() {
            for now := range t.C {
                fmt.Println("Ticker")
            }
        }()
    })
time.AfterFunc(time.Second*5, func() {
        fmt.Println("AfterFunc")

        t := time.NewTicker(time.Second * 2)
        defer t.Stop()
        for range t.C {
            fmt.Println("Ticker")
        }
    })

It produces the output you need:

starting up
AfterFunc
Ticker
Ticker
Ticker
Ticker
Ticker

The Go Programming Language Specification

Program execution

Program execution begins by initializing the main package and then invoking the function main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.

time.AfterFunc(time.Second * 5, func() {
    fmt.Println("AfterFunc")
    t := time.NewTicker(time.Second * 2)
    defer t.Stop()
    go func() {
        for now := range t.C {
            fmt.Println("Ticker")
        }
    }()
})

defer t.Stop() stops the ticker.

You are not waiting for the goroutine to run.

Gosh, I figured it out not long after posting, though I still think my solution lacks some elegance.

As @peterSO pointed out, the function passed to AfterFunc() executes and stops the Ticker moments after creating it with defer t.Stop()

The solution for me was to define the t variable before the call to AfterFunc() so that it has scope outside of the AfterFunc() payload function, and then stop it at the end of my main() func. Here is the new main() func:

func main() {
    fmt.Println("starting up")

    var t *time.Ticker
    time.AfterFunc(time.Second * 5, func() {
        fmt.Println("AfterFunc")

        t = time.NewTicker(time.Second * 2)
        go func() {
            for now := range t.C {
                fmt.Println("Ticker")
            }
        }()
    })
    //defer t.Stop()

    // Block until termination signal is received
    osSignals := make(chan os.Signal, 1)
    signal.Notify(osSignals, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
    <-osSignals

    t.Stop()

    logger.Info("exiting gracefully")
}

Strangely though that commented out defer t.Stop() causes a panic (invalid memory address or nil pointer dereference) when the application closes upon a termination signal. If I stop it as in the uncommented t.Stop() at the very end of the code, it works as expected. Not sure why that is.