How to schedule a task at go periodically?


(Siddhanta Rath) #1

Is there any native library or third party support like ScheduledExecutorService by java native library at go lang for production use case?
Please find the code snippet in java 1.8 :

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


public class TaskScheduler {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Runnable runnable = ()-> {
                // task to run goes here
                System.out.println("Hello !!");
        };
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);

    }

}

Looking for idiomatic go implementation with no memory leakage :slight_smile:
obviously not looking for solution like this :

go func() {
    for true {
        fmt.Println("Hello !!")
        time.Sleep(1 * time.Second)
    }
}()

(Johan Dahl) #2

You should use time.Ticker

https://gobyexample.com/tickers

https://golang.org/pkg/time/#example_NewTicker


(Boban Acimovic) #3

There is also a cron like lib


(Siddhanta Rath) #4

Thanks :slight_smile:
Ticker is the perfect solution to my problem.
Two quick question :
1 - Ticker doesn’t get invoke as soon as the application starts. It always wait for interval time given at ticker creation.
Let’s say ,if i want to execute some task at given interval of 15 mins.For the first time execution,Ticker waits 15 min to invoke the task after application start.
I found a hack way by calling the task for the first time from main and then rely on ticker to trigger the task in each 15 mins.In javaScheduledExecutorService, we can achieve the behavior by just passing ‘0’ as delay.
Is there any way to do this with ticker ?
2 - I am stopping the ticker and exiting main only after some os interrupts. will it lead to high CPU and resource utilization ?
code snippet :

func main() {
	task(time.Now())
	tick := time.NewTicker(time.Second * 5)
	go scheduler(tick)
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	 <-sigs
	tick.Stop()
	//os.Exit(1)
}
func scheduler(tick *time.Ticker) {
	for t := range tick.C {
		task(t)
	}
}

func task(t time.Time) {
	fmt.Println("hello! printed at ", t)
}

(Boban Acimovic) #5
  1. You can start ticker immediately using this:

    ticker := time.NewTicker(time.Second * 5)
    for ; true; <-ticker.C {

    }

  2. Ticker blocks during the wait time, so it is not heavy, scheduler will run other goroutines during this time. Unless you put 1 nanosecond as wait time :smile:


(Siddhanta Rath) #6
  1. Neat solution :smile:
  2. okay, according to the official spec, it says “Stop the ticker to release associated resources.”
    but at my implementation “stop” is getting called after signal interrupt. So, i thought it might cause some high resource utilization. Thanks for the explanation :slight_smile:

(Boban Acimovic) #7
  1. In your case you don’t have to stop it because when you exit your program it will stop anyways. You usually have to stop it in forever loops when you want to reuse the same ticker.

(Siddhanta Rath) #8

In this above example, I don’t think ,they are reusing the ticker.Still, they stopping the ticker by defer. All, I need to know, is it best practice to stop it always? because i don’t see any harm on not stopping the ticker, as you said, it will stop anyways after main exit.

If it is the practice, then i have to, otherwise code won’t pass the review :slightly_frowning_face:


(Boban Acimovic) #9

Well, I agree with that, you should write it correctly :smile:

Take care of this as well (for timers generally):


(Johan Dahl) #10

You could move the first task into the scheduler function

func scheduler(tick *time.Ticker) {
	task(time.Now())
	for t := range tick.C {
		task(t)
	}
}

so is it kept together with the other invocations of the tasks

If a task must finish before you exits could you also create a channel which the scheduler waits for and it can recieve on then the task is finished

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	tick := time.NewTicker(time.Second * 5)
	done := make(chan bool)
	go scheduler(tick, done)
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	<-sigs
	done <- true
	//os.Exit(1)
}

func scheduler(tick *time.Ticker, done chan bool) {
	task(time.Now())
	for {
		select {
		case t := <-tick.C:
			task(t)
		case <-done:
			return
		}
	}
}

func task(t time.Time) {
	fmt.Println("work started at", t)
	time.Sleep(2 * time.Second)
	fmt.Println("work finished")
}

If you try to stop this you will notice the program doesn’t end until the last work finished has been written.