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.


(Siddhanta Rath) #11

@johandalabacka
Awesome, I have used the second solution.It always wait for task to be finished.
some doubts though:
1: after receiving true from done chan at scheduler func ,shouldn’t it return from the function immediately and exit the for loop ? why it is waiting for unfinished task?
2: ticker has not been stopped,is it intentional ?


(Johan Dahl) #12
  1. But it does this by calling return or do I missunderstand you?

  2. you could stop it but as stated in an earlier post. It is only necessary if you’re going to start it again later and the program just exits shortly afterwards.


(Siddhanta Rath) #13
  1. Select always block the execution, till it gets some value from either channel.
    My question was : if done becomes true , for block should exit immediately by return. why is it waiting for unfinished task to be finished which was triggered by ticker channel previously?

  2. Got it :slight_smile:


(Johan Dahl) #14
  1. done <- true in the main go routine will block until the ongoing task has run and select reads from done. You should use this pattern if the the task can’t be interrupted in the middle. Maybe it has to finish some database operation or writing to a file. If it is ok that the task is aborted at the same time as the program will you not need this construct with the done channel.

(Siddhanta Rath) #15

Now got it :smile: