Reputation: 399
I have a list of functions and their respective intervals. I want to run each function at its interval concurrently.
In JavaScript, I wrote something like:
maps.forEach(({fn, interval}) => {
setInterval(fn, interval)
})
How do I implement this functionality in Golang?
Upvotes: 2
Views: 6661
Reputation: 418435
Use a time.Ticker
to receive "events" periodically, which you may use to time the execution of a function. You may obtain a time.Ticker
by calling time.NewTicker()
. The returned ticker has a channel on which values are sent periodically.
Use a goroutine to continuously receive the events and call the function, e.g. with a for range
loop.
Let's see 2 functions:
func oneSec() {
log.Println("oneSec")
}
func twoSec() {
log.Println("twoSec")
}
Here's a simple scheduler that periodically calls a given function:
func schedule(f func(), interval time.Duration) *time.Ticker {
ticker := time.NewTicker(interval)
go func() {
for range ticker.C {
f()
}
}()
return ticker
}
Example using it:
func main() {
t1 := schedule(oneSec, time.Second)
t2 := schedule(twoSec, 2*time.Second)
time.Sleep(5 * time.Second)
t1.Stop()
t2.Stop()
}
Example output (try it on the Go Playground):
2009/11/10 23:00:01 oneSec
2009/11/10 23:00:02 twoSec
2009/11/10 23:00:02 oneSec
2009/11/10 23:00:03 oneSec
2009/11/10 23:00:04 twoSec
2009/11/10 23:00:04 oneSec
Note that Ticker.Stop()
does not close the ticker's channel, so a for range
will not terminate; Stop()
only stops sending values on the ticker's channel.
If you want to terminate the goroutines used to schedule the function calls, you may do that with an additional channel. And then those goroutines may use a select
statement to "monitor" the ticker's channel and this done
channel, and return if receiving from done
succeeds.
For example:
func schedule(f func(), interval time.Duration, done <-chan bool) *time.Ticker {
ticker := time.NewTicker(interval)
go func() {
for {
select {
case <-ticker.C:
f()
case <-done:
return
}
}
}()
return ticker
}
And using it:
func main() {
done := make(chan bool)
t1 := schedule(oneSec, time.Second, done)
t2 := schedule(twoSec, 2*time.Second, done)
time.Sleep(5 * time.Second)
close(done)
t1.Stop()
t2.Stop()
}
Try this one on the Go Playground.
Note that even though stopping the tickers is not necessary in this simple example (because when the main
goroutine ends, so does the program with it), in real-life examples if the app continues to run, leaving the tickers unstopped wastes resources (they will continue to use a background goroutine, and will continue to try to send values on their channels).
Last words:
If you have a slice of function-interval pairs, simply use a loop to pass each pair to this schedule()
function. Something like this:
type pair struct {
f func()
interval time.Duration
}
pairs := []pair{
{oneSec, time.Second},
{twoSec, 2 * time.Second},
}
done := make(chan bool)
ts := make([]*time.Ticker, len(pairs))
for i, p := range pairs {
ts[i] = schedule(p.f, p.interval, done)
}
time.Sleep(5 * time.Second)
close(done)
for _, t := range ts {
t.Stop()
}
Try this one on the Go Playground.
Upvotes: 15