What is the best way to call N concurrent functions periodically in Go?

Issue

I’ve been struggling with a problem for the past day or so in figuring out the best way to create N concurrent functions which are called periodically at the same interval in Go. I want to be able to specify an arbitrary number of functions, have them all run periodically simultaneously, and end them all after a specified amount of time.

Right now I have a solution which works but a new ticker has to be created for each concurrent function. I’m also not sure how to use sync.WaitGroup properly, as my current implementation results in the program never ending (just gets stuck on wg.Wait() at the end)

I briefly looked at a ticker wrapper called Multitick, but I’m not sure how to implement it. Maybe Multitick could be the solution here?

func main() {
    N := 10

    var wg sync.WaitGroup
    wg.Add(N)

    quit := make(chan struct{})

    for i := 0; i < N; i++ {

        tick := time.NewTicker(500 * time.Millisecond)
        go func(t *time.Ticker) {

            for a := range tick.C {

                select {
                case <-quit:
                    break
                default:
                    fmt.Println(a) // do something on tick
                }
            }
            wg.Done()
        }(tick)
    }

    time.Sleep(10 * time.Second)
    close(quit)
    wg.Wait()
}

Go Playground Demo

So this solution works, executing all of the tickers concurrently at the proper intervals and finishing after 10 seconds, but it doesn’t actually exit the program, hanging up on the wg.Wait() line at the end. Additionally, each concurrent function call uses its own ticker- is there any way I can have one “master” ticker that all of the functions operate from?

Thanks in advance! This is my first time really delving in to concurrency in Go.

Solution

The reason that your program never exits is a strange quirk of the Go language: the break statement for case <-quit quits the select statement, instead of the loop. (Not sure why this behaviour would ever be useful.) To fix your program, you need to explicitly break the loop:

tickLoop:
    for a := range tick.C {
        select {
        case <-quit:
            break tickLoop
        default:
            fmt.Println(a, "function #", id) // do something on tick
        }
    }

As the code is written, it always waits until the next tick before quitting. You can fix this, by reading tick.C in the select statement, too:

tickLoop:
    for {
        select {
        case <-quit:
            break tickLoop
        case a := <-tick.C:
            fmt.Println(a, "function #", id) // do something on tick
        }
    }

Finally, if you want to restructure your program to use only one ticker, you can start an extra goroutine, which listens on the ticker and on the quit channel, and then starts N sub-goroutines on every tick.

Answered By – jochen

Answer Checked By – Katrina (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.