Reputation: 53
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()
}
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.
Upvotes: 5
Views: 395
Reputation: 3940
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.
Upvotes: 5