5

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.

Nate Mela
  • 53
  • 3
  • Your implementation gives you independent timers for each, which doesn't sound like what you want, and isn't really compatible with using a `WaitGroup`. It sounds like what you want is a single `Ticker`, and each time it fires, you want to concurrently call all 3 functions. – Adrian Jun 13 '19 at 19:27
  • @Adrian I didn't think of that approach! I like it a lot though. I tried my hand at [writing a version of it](https://play.golang.org/p/ohFFI0alQxb), but I can't figure out why it results in a "fatal error: all goroutines are asleep - deadlock!" – Nate Mela Jun 13 '19 at 20:34
  • @Adrian This works perfectly now, I found what was wrong. Thank you! Here's updated code: https://play.golang.org/p/Q7Dv0bPLnwe – Nate Mela Jun 13 '19 at 20:41

1 Answers1

5

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.

jochen
  • 3,728
  • 2
  • 39
  • 49
  • 1
    Thank you! Exactly what I was looking for. I didn't know you could label loops like that to explicitly break them – Nate Mela Jun 14 '19 at 17:31