Best/most resource-efficient way to run a function every second?

Good morning,

I want to implement a status script on my server and I’m looking for the best way to run the function every second.

I came up with the following code but I don’t know if it’s the best way:

package main

import (
	"fmt"
	"time"
)

func main() {
	iteration := 0
	timer := time.NewTicker(time.Millisecond * 1000)

	go func() {
		for t := range timer.C {
			fmt.Println("Connect to local application and check status at", t)

			iteration++
			if iteration == 10 {
				fmt.Println("Connect to main status server.")
				iteration = 0
			}
		}
	}()

	<-make(chan struct{})
	return
}

Is that ok like this or is there a better way?

I would write it this way:

package main

import (
	"fmt"
	"time"
)

func main() {
	go func() {
		iteration := 0
		for t := range time.Tick(time.Second) {
			fmt.Println("Connect to local application and check status at", t)

			iteration++
			if iteration == 10 {
				fmt.Println("Connect to main status server.")
				iteration = 0
			}
		}
	}()

	select {}
}

Moving iteration to the goroutine prevents it from escaping to the heap. This can be checked by building with go build -gcflags="-m" main.go. Leaving iteration in main()'s scope also gives the potential for a race condition.

Since the ticker was only used as the for loop’s range, I moved it there. time.Tick is a convenience function around time.NewTicker that only returns the channel. Note that with this new setup there is no way to shut the ticker down. That is probably not a problem because the server should always report its health.

I used time.Second instead of basing on milliseconds because it is easier to read.

I used select{} instead of <-make(chan struct{}) because it is more efficient. It basically tells the scheduler that the function will block forever. Creating a channel and then reading from it creates a condition in which the function could continue that must then be checked.

There is no reason to return at the end of a function that does not return anything. Executing to the end of the function body has the same effect. Also, the return would never be encountered because nothing could be sent to the channel and so nothing could be read from it, blocking forever.

An alternative to having the server push it status to the status server is for the status server to pull the status to it. This is how https://prometheus.io deals with monitoring and alerting. https://prometheus.io/blog/2016/07/23/pull-does-not-scale-or-does-it/ and https://thenewstack.io/exploring-prometheus-use-cases-brian-brazil/ discuss some of the tradeoffs.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.