如何使用定时器在第一次循环时立即运行的方式来实现for循环机制?

huangapple go评论73阅读模式
英文:

How to implement a for loop mechanism with a timer that runs immediately in the first loop?

问题

我想编写一个在Kubernetes容器中作为服务运行的Golang程序。该程序应该始终运行,并且不会自行终止,除非发生错误。如果来自kubelet / Kubernetes的SIGTERM信号表示Pod将被删除,程序应该退出。

在我的第一个尝试中,我实现了一个for循环,每分钟运行一次。在这个循环中,程序从另一个服务查询资源并进行处理。然后,程序应该在执行下一步之前“休眠”一分钟。在“休眠”阶段,如果收到SIGTERM信号,程序应立即响应。

我目前的方法有两个问题。首先,第一次运行for循环时,我必须等待一分钟。我能否以某种方式配置它,使得计时器只在第一次运行后才开始运行?

第二个问题是,在运行之间,程序不应执行任何操作,除非响应SIGTERM信号。

我不确定我的方法是否正确。我刚开始学习GO语言。也许对于我的问题有更好的方法。

英文:

I would like to write a golang program that runs as a service in a container in kubernetes. The program should run all the time and not terminate itself - except in the event of an error. If a SIGTERM comes from kubelet / kubernetes because the pod should be deleted, the program should exit.

In my first approach, I implemented a for loop that should be run through every minute. In this loop, the program queries a resource from another service and processes it. The service should then "sleep" for a minute before carrying out the steps again. During the "sleep" phase, the program should immediately repond if there is a SIGTERM.

func main() {
  
  c := make(chan os.Signal, 1)
  signal.Notify(c, os.Interrupt, syscall.SIGTERM)

  //restart loop every minute 
  ticker := time.NewTicker(60 * time.Second)
  defer ticker.Stop()

  // while true loop
  for {
	select {
	case <-c:
	  fmt.Println("Break the loop")
	  return
	case <-ticker.C:
      fmt.Println("Hello in a loop")
    }
  }
}

I have two problems with my current approach. Firstly, I have to wait a minute the first time I run the for loop. Can I somehow configure it so that the timer only runs after the first run?

The second problem is that the program should not do anything between runs - except react to SIGTERM.

I'm not sure if my approach to the problem is the right one. I'm just starting out with the GO language. Perhaps there is a much better approach to my problem.

答案1

得分: 2

你可以在select语句之外执行你的工作,并在ticker通道的情况下继续执行。这样一来,你的工作将立即执行一次,然后每次ticker滴答时都会执行一次。

你的函数看起来不错,但是最好在可以的情况下使用context。许多包(数据库、IO等)都有指定上下文的选项,通常用于定义超时。在你的情况下,将相同的(或子)上下文传递给这些包将意味着它们也会遵守sigterm

package main

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

func main() {
	ctx := cancelCtxOnSigterm(context.Background())
	startWork(ctx)
}

// cancelCtxOnSigterm返回一个在程序接收到sigterm时将被取消的上下文。
func cancelCtxOnSigterm(ctx context.Context) context.Context {
	exitCh := make(chan os.Signal, 1)
	signal.Notify(exitCh, os.Interrupt, syscall.SIGTERM)

	ctx, cancel := context.WithCancel(ctx)
	go func() {
		<-exitCh
		cancel()
	}()
	return ctx
}

// startWork每60秒执行一次任务,直到上下文完成。
func startWork(ctx context.Context) {
	ticker := time.NewTicker(60 * time.Second)
	defer ticker.Stop()
	for {
		// 在这里执行工作,这样我们就不需要重复调用。它将立即运行,并且随着循环的继续,每分钟运行一次。
		if err := work(ctx); err != nil {
			fmt.Printf("failed to do work: %s", err)
		}
		select {
		case <-ticker.C:
			continue
		case <-ctx.Done():
			return
		}
	}
}

func work(ctx context.Context) error {
	fmt.Println("doing work")
	return nil
}
英文:

You can do your work outside of the select statement, and just continue inside of the ticket channel case. This will execute your work once immediately, then every time the ticker ticks.

Your function looks good, but it is good practice to make use of context when you can.

Lot's of packages (database, IO, etc) have the option of specifying a context, which is often used to define timeouts. In your case, passing the same (or child) context to these packages would mean that they also respect sigterm.

package main

import (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;os/signal&quot;
	&quot;syscall&quot;
	&quot;time&quot;
)

func main() {
	ctx := cancelCtxOnSigterm(context.Background())
	startWork(ctx)
}

// cancelCtxOnSigterm returns a Context that will be cancelled when the program receives a sigterm.
func cancelCtxOnSigterm(ctx context.Context) context.Context {
	exitCh := make(chan os.Signal, 1)
	signal.Notify(exitCh, os.Interrupt, syscall.SIGTERM)

	ctx, cancel := context.WithCancel(ctx)
	go func() {
		&lt;-exitCh
		cancel()
	}()
	return ctx
}

// startWork performs a task every 60 seconds until the context is done.
func startWork(ctx context.Context) {
	ticker := time.NewTicker(60 * time.Second)
	defer ticker.Stop()
	for {
		// Do work here so we don&#39;t need duplicate calls. It will run immediately, and again every minute as the loop continues.
		if err := work(ctx); err != nil {
			fmt.Printf(&quot;failed to do work: %s&quot;, err)
		}
		select {
		case &lt;-ticker.C:
			continue
		case &lt;-ctx.Done():
			return
		}
	}
}

func work(ctx context.Context) error {
	fmt.Println(&quot;doing work&quot;)
	return nil
}

huangapple
  • 本文由 发表于 2022年6月1日 22:35:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/72463665.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定