“Do a thing, sleep a while, repeat” is an extremely common paradigm. Here is how that concept is typically expressed:
for {
// this takes anywhere from a few milliseconds, to several seconds (or more)
doOperation()
time.Sleep(time.Second * 60) // do it every minute
}
The trouble is, it’s not really doing it every minute, it’s sleeping for
a minute. Over time, depending on how long doOperation()
takes, the
consistency of your repetitions wanders, which might actually become problematic.
For example, say you have to record a data point every minute. If doOperation()
runs
at 04:59:58 and takes 3 seconds, the next call will happen at 5:51:01 - skipping
the entire minute starting at 5:50.
For example:
package main
import (
"log"
"math/rand"
"time"
)
func main() {
for {
log.Print("loop starting")
doOperation()
// wait and repeat
time.Sleep(time.Second * 60)
}
}
func doOperation() {
// simulate indeterminate-length operation, 0-3 seconds ish
time.Sleep(time.Millisecond * time.Duration(rand.Intn(3000)))
}
Might produce:
2022/04/03 10:59:57 loop starting
2022/04/03 11:00:59 loop starting
2022/04/03 11:02:01 loop starting
2022/04/03 11:03:03 loop starting
We missed a whole minute. Additionally (if you’re like me), it just looks offensive.
Of course, one could do some simple duration maths, calculating how long the operation took and sleeping for an appropriate time. But there is a much easier way:
func main() {
for {
timer := time.NewTimer(time.Second * 60)
log.Print("loop starting")
doOperation()
// wait until timer expires
<-timer.C
}
}
Produces:
2022/04/03 11:06:51 loop starting
2022/04/03 11:07:51 loop starting
2022/04/03 11:08:51 loop starting
2022/04/03 11:09:51 loop starting
Beautiful.
This is the simplest use case of the time.Timer
. Because it just exposes a
channel which returns a time.Time
value on timer expiry, you can do a lot more
than this simple case.