How to schedule a task at go periodically? - go

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);
}
}
It will print Hello !! in every one second.

No need to use 3rd party library to achieve that. Simply take the advantage of goroutine and use available time.Sleep() API from time package, then the very same result can be achieved.
Example:
go func() {
for true {
fmt.Println("Hello !!")
time.Sleep(1 * time.Second)
}
}()
Playground: https://play.golang.org/p/IMV_IAt-VQX
Example using ticker #1
As per suggestion from Siddhanta. Here is one example to achieve the same result by using ticker (taken from go documentation page of ticker, with some modifications following your requirement).
done := make(chan bool)
ticker := time.NewTicker(1 * time.Second)
go func() {
for {
select {
case <-done:
ticker.Stop()
return
case <-ticker.C:
fmt.Println("Hello !!")
}
}
}()
// wait for 10 seconds
time.Sleep(10 *time.Second)
done <- true
The ticker time information (the time when the Hello !! executed) can be taken from ticker.C channel.
case t := <-ticker.C:
fmt.Println(t)
Playground: https://play.golang.org/p/TN2M-AMr39L
Example using ticker #2
Another simplified example of ticker, taken from https://gobyexample.com/tickers
ticker := time.NewTicker(1 * time.Second)
go func() {
for range ticker.C {
fmt.Println("Hello !!")
}
}()
// wait for 10 seconds
time.Sleep(10 *time.Second)
ticker.Stop()

Related

Exit a goroutine after a timeout

I'm trying to write a program in go that is similar to cron with the addition that jobs are given a max runtime and if a function exceeds this duration, the job should exit. Here is my my whole code:
package main
import (
"fmt"
"log"
"sync"
"time"
)
type Job struct {
ID string
MaxRuntime time.Duration
Frequency time.Duration
Function func()
}
func testFunc() {
log.Println("OPP11")
time.Sleep(7 * time.Second)
log.Println("OP222")
}
func New(ID, frequency, runtime string, implementation func()) Job {
r, err := time.ParseDuration(runtime)
if err != nil {
panic(err)
}
f, err := time.ParseDuration(frequency)
if err != nil {
panic(err)
}
j := Job{ID: ID, MaxRuntime: r, Frequency: f, Function: implementation}
log.Printf("Created job %#v with frequency %v and max runtime %v", ID, f, r)
return j
}
func (j Job) Run() {
for range time.Tick(j.Frequency) {
start := time.Now()
log.Printf("Job %#v executing...", j.ID)
done := make(chan int)
//quit := make(chan int)
//var wg sync.WaitGroup
//wg.Add(1)
go func() {
j.Function()
done <- 0
}()
select {
case <-done:
elapsed := time.Since(start)
log.Printf("Job %#v completed in %v \n", j.ID, elapsed)
case <-time.After(j.MaxRuntime):
log.Printf("Job %#v halted after %v", j.ID, j.MaxRuntime)
// here should exit the above goroutine
}
}
}
func main() {
// create a new job given its name, frequency, max runtime
// and the function it should run
testJob := New("my-first-job", "3s", "5s", func() {
testFunc()
})
testJob.Run()
}
What I'm trying to do is that in the second case in the select of the Run() function, it should exit the goroutine which is running the function. I tried to do this by wrapping the function in a for loop with a select statement which listens on a quit channel like this:
go func() {
for {
select {
case <-quit:
fmt.Println("quiting goroutine")
return
default:
j.Function()
done <- 0
}
}
}()
And then having quit <- 1 in the Run() function, but that doesnt seem to be doing anything. Is there a better of doing this?
As explained in the comments, the whole problem is that you want to cancel the execution of a function (j.Function) that isn't cancellable.
There's no way to "kill a goroutine". Goroutines work in a cooperative fashion. If you want to be able to "kill it", you need to ensure that the function running in that Goroutine has a mechanism for you to signal that it should stop what it's doing and return, letting the Goroutine that was running it finally terminate.
The standard way of indicating that a function is cancellable is by having it take a context.Context as its first param:
type Job struct {
// ...
Function func(context.Context)
}
Then you create the context and pass it to the j.Function. Since your cancellation logic is simply based on a timeout, there's no need to write all that select ... case <-time.After(...), as that is provided as built-in functionality with a context.Context:
func (j Job) Run() {
for range time.Tick(j.Frequency) {
go j.ExecuteOnce()
}
}
func (j Job) ExecuteOnce() {
log.Printf("Job %#v executing...", j.ID)
ctx, cancel := context.WithTimeout(context.Background(), j.MaxRuntime)
defer cancel()
j.Function(ctx)
}
Now, to finish, you have to rewrite the functions that you're going to be passing to your job scheduler so that they take context.Context and, very importantly, that they use it properly and cancel whatever they're doing when the context is cancelled.
This means that if you're writing the code for those funcs and they will somehow block, you'll be responsible for writing stuff like:
select {
case <-ctx.Done():
return ctx.Err()
case ...your blocking case...:
}
If your funcs are invoking 3rd party code, then that code needs to be aware of context and cancellation, and you'll need to pass down the ctx your funcs receive.

How to pause and resume goroutine?

I am trying to pause and resume groutine. I understand I can sleep the run, but I am looking for is like a button "pause/resume" rather than a timer.
Here is my attempt. I am using the blocking feature of channel to pause, and select to switch what to execute based on channel value. However, the output is always Running in my case.
func main() {
ctx := wctx{}
go func(ctx wctx) {
for {
time.Sleep(1 * time.Second)
select {
case <-ctx.pause:
fmt.Print("Paused")
<-ctx.pause
case <-ctx.resume:
fmt.Print("Resumed")
default:
fmt.Print("Running \n")
}
}
}(ctx)
ctx.pause <- struct{}{}
ctx.resume <- struct{}{}
}
type wctx struct {
pause chan struct{}
resume chan struct{}
}
A select with multiple ready cases chooses one pseudo-randomly. So if the goroutine is "slow" to check those channels, you might send a value on both pause and resume (assuming they are buffered) so receiving from both channels could be ready, and resume could be chosen first, and in a later iteration the pause when the goroutine should not be paused anymore.
For this you should use a "state" variable synchronized by a mutex. Something like this:
const (
StateRunning = iota
StatePaused
)
type wctx struct {
mu sync.Mutex
state int
}
func (w *wctx) SetState(state int) {
w.mu.Lock()
defer w.mu.Unlock()
w.state = state
}
func (w *wctx) State() int {
w.mu.Lock()
defer w.mu.Unlock()
return w.state
}
Testing it:
ctx := &wctx{}
go func(ctx *wctx) {
for {
time.Sleep(1 * time.Millisecond)
switch state := ctx.State(); state {
case StatePaused:
fmt.Println("Paused")
default:
fmt.Println("Running")
}
}
}(ctx)
time.Sleep(3 * time.Millisecond)
ctx.SetState(StatePaused)
time.Sleep(3 * time.Millisecond)
ctx.SetState(StateRunning)
time.Sleep(2 * time.Millisecond)
Output (try it on the Go Playground):
Running
Running
Running
Paused
Paused
Paused
Running
Running
You need to initialize your channels, remember that reads from nil channels always blocks.
A select with a default case never blocks.
Here is a modified version of your program, that fixes the above mentioned issues:
package main
import (
"fmt"
"time"
)
func main() {
ctx := wctx{
pause: make(chan struct{}),
resume: make(chan struct{}),
}
go func(ctx wctx) {
for {
select {
case <-ctx.pause:
fmt.Println("Paused")
case <-ctx.resume:
fmt.Println("Resumed")
}
fmt.Println("Running")
time.Sleep(time.Second)
}
}(ctx)
ctx.pause <- struct{}{}
ctx.resume <- struct{}{}
}
type wctx struct {
pause chan struct{}
resume chan struct{}
}

Running multiple methods periodically

I'm new to go and trying to make two methods run at the same time periodically for as long at the application is running. I've managed to come up with the following but the for true part does not feel right as this is blocking.
Would channels be a better way todo this? Any pointers in the right direction would be helpful.
func main() {
t1 := schedule(ping, time.Second)
t2 := schedule(ping, 2*time.Second)
for true {
time.Sleep(1 * time.Second)
}
t1.Stop()
t2.Stop()
}
func schedule(f func(interval time.Duration), interval time.Duration) *time.Ticker {
ticker := time.NewTicker(interval)
go func() {
for range ticker.C {
f(interval)
}
}()
return ticker
}
func ping(interval time.Duration) {
log.Println("ping ", interval)
}
To prevent the application from exiting, the main goroutine must block.
Use select {} to block the main goroutine.
Because the tickers run for the duration of the application, there's no need to stop the tickers.
func main() {
schedule(ping, time.Second)
schedule(ping, 2*time.Second)
select {}
}

Should we stop this kind of Ticker?

I have a time ticker that will execute a function within time interval(eg every 5 minutes, 10 minutes). I create this time ticker within a goroutine. I heard that this kind of ticker could leak the memory even if the app stopped. This ticker will keep running as long as the app running. Should it stop? how to stop it properly? Here is my implementation:
go func() {
for range time.Tick(5 * time.Minute) {
ExecuteFunctionA()
}
}()
What is the proper implementation for time ticker like this?
You can use a channel as a intermediary to stop the ticker properly.
Usually, I did it as the following:
var stopChan chan bool = make(chan bool)
func Stop() {
stopChan <- true
}
func Run() {
go func() {
ticker := time.NewTicker(5 * time.Minute)
for {
select {
case <- c.stopChan:
ticker.Stop()
return
case <- ticker.C:
ExecuteFunctionA()
}
}
}()
}
And you can invoke Stop function at the time you want to stop the ticker safely.
Nothing will leak if the 'app stopped'. The warning in the documentation refers to the fact that the garbage collector will not be able to reclaim the channel once it is created (time.Tick() initializes and returns a chan) and it will sit in memory even if you decide to break out of your for loop.
Based on your description in the question, this shouldn't be an issue for you since you want the ticker running as long as the app is running. But if you decide otherwise, you can use an alternative way like:
go func() {
for {
time.Sleep(time.Duration(5) * time.Minute)
go ExecuteFunctionA()
if someConditionIsMet {
break // nothing leaks in this case
}
}
}()

CRON JOB in GOLANG

I AM USING IN CRON PKG https://github.com/jasonlvhit/gocron/blob/master/gocron.go
import (
"fmt"
"time"
"github.com/claudiu/gocron"
)
func task() {
fmt.Println("I am runnning task.", time.Now())
}
func vijay() {
fmt.Println("I am runnning vijay.", time.Now())
}
func main() {
go test()
gocron.Start()
s := gocron.NewScheduler()
gocron.Every(5).Seconds().Do(task)
gocron.Every(10).Seconds().Do(vijay)
<-s.Start()
}
func test() {
time.Sleep(20 * time.Second)
gocron.Clear()
fmt.Println("All task removed")
}
My problem is after removing all job, my program is still executing
i want to break the exection after removing all jobs
please help me out ,i am not able to find out how to do it ,
i tried to change the PKG source code also but not able to find out the way to do it
thank you all
First, you're creating a new scheduler, and waiting on it, but using the default scheduler to run your jobs.
Next, you're blocking on the channel returned by the Start() method. Close that channel to unblock the receive operation. This will also exit the main loop in the cron program if you aren't immediately exiting from main.
func main() {
ch := gocron.Start()
go test(ch)
gocron.Every(5).Seconds().Do(task)
gocron.Every(10).Seconds().Do(vijay)
<-ch
}
func test(stop chan bool) {
time.Sleep(20 * time.Second)
gocron.Clear()
fmt.Println("All task removed")
close(stop)
}
which effectively is the same as
func main() {
gocron.Start()
gocron.Every(5).Seconds().Do(task)
gocron.Every(10).Seconds().Do(vijay)
time.Sleep(20 * time.Second)
gocron.Clear()
fmt.Println("All task removed")
}
If you're exiting immediately, it doesn't really matter if you call Clear() first and then stop the scheduler, you can simply exit the program.
JimB rights. But I don't know why do you use gocron methods and the s methods. This example works fine:
package main
import (
"fmt"
"time"
"github.com/claudiu/gocron"
)
func task() {
fmt.Println("I am runnning task.", time.Now())
}
func vijay() {
fmt.Println("I am runnning vijay.", time.Now())
}
func main() {
s := gocron.NewScheduler()
s.Every(2).Seconds().Do(task)
s.Every(4).Seconds().Do(vijay)
sc := s.Start() // keep the channel
go test(s, sc) // wait
<-sc // it will happens if the channel is closed
}
func test(s *gocron.Scheduler, sc chan bool) {
time.Sleep(8 * time.Second)
s.Clear()
fmt.Println("All task removed")
close(sc) // close the channel
}

Resources