每隔N秒运行函数,并设置上下文超时。

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

Run function every N seconds with context timeout

问题

我对“可取消”goroutine的调度有一个基本问题。

我想要每3秒执行一次函数。

该函数可能需要最多5秒的时间。

如果函数执行时间超过2999毫秒,我想要停止/终止它,以避免与下一个函数重叠。

我现在的做法是错误的:

func main() {
    fmt.Println("startProcessing")
    go startProcessing()

    time.Sleep(time.Second * 60)
    fmt.Println("endProcessing after 60s")
}

func startProcessing() {
    ticker := time.NewTicker(3 * time.Second)
    for _ = range ticker.C {
        ctx, _ := context.WithTimeout(context.Background(), (time.Second*3)-time.Millisecond)

        fmt.Println("start doSomething")
        doSomething(ctx)
    }
}

func doSomething(ctx context.Context) {
    executionTime := time.Duration(rand.Intn(5)+1) * time.Second

    for {
        select {
        case <-ctx.Done():
            fmt.Printf("timed out after %s\n", executionTime)
            return
        default:
            time.Sleep(executionTime)
            fmt.Printf("did something in %s\n", executionTime)
            return
        }
    }
}

这是我的当前输出:

startProcessing

start doSomething

did something in 2s

start doSomething

did something in 3s

start doSomething

did something in 3s

start doSomething

did something in 5s

start doSomething

did something in 2s

...

我想要读取timed out after 5s而不是did something in 5s

英文:

I have a basic question about scheduling "cancellable" goroutines.

I want to schedule a function execution, every 3 seconds.

The function can take up to 5 seconds.

In case it takes more than 2999ms I want to stop/terminate it, to avoid overlapping w/ the next one.

I'm doing it wrong:

func main() {
	fmt.Println(&quot;startProcessing&quot;)
	go startProcessing()

	time.Sleep(time.Second * 60)
	fmt.Println(&quot;endProcessing after 60s&quot;)
}

func startProcessing() {
	ticker := time.NewTicker(3 * time.Second)
	for _ = range ticker.C {
		ctx, _ := context.WithTimeout(context.Background(), (time.Second*3)-time.Millisecond)

		fmt.Println(&quot;start doSomething&quot;)
		doSomething(ctx)
	}
}

func doSomething(ctx context.Context) {
	executionTime := time.Duration(rand.Intn(5)+1) * time.Second

	for {
		select {
		case &lt;-ctx.Done():
			fmt.Printf(&quot;timed out after %s\n&quot;, executionTime)
			return
		default:
			time.Sleep(executionTime)
			fmt.Printf(&quot;did something in %s\n&quot;, executionTime)
			return
		}
	}
}

This is my output now:

> startProcessing
>
> start doSomething
>
> did something in 2s
>
> start doSomething
>
> did something in 3s
>
> start doSomething
>
> did something in 3s
>
> start doSomething
>
> did something in 5s
>
> start doSomething
>
> did something in 2s
>
> ...

I want to read timed out after 5s instead of did something in 5s.

答案1

得分: 2

你只需要将time.Sleep(executionTime)放在select之外,不需要for循环。我认为这可能是你想要的,但要注意这不是一个好的做法。所以请看下面的警告

func doSomething(ctx context.Context) {
	executionTime := time.Duration(rand.Intn(5)+1) * time.Second
	processed := make(chan int)

	go func() {
		time.Sleep(executionTime)
		processed <- 1
	}()

	select {
	case <-ctx.Done():
		fmt.Printf("在 %s 后超时\n", executionTime)
	case <-processed:
		fmt.Printf("在 %s 内完成了某些操作\n", executionTime)
	}
}

注意: 我稍微修改了原始答案。我们不能在goroutine执行的过程中中断它。我们可以将耗时的任务委托给另一个goroutine,并通过一个专用的通道接收结果。

警告: 如果你预计处理时间将超过截止时间,我不建议这样做,因为现在你将会有一个泄漏的goroutine。

英文:

You just need to put the time.Sleep(executionTime) outside the select and there is no need for the for loop. I think this is somehow what you want but beware that it's not good practice. So take a look at the warning below.

func doSomething(ctx context.Context) {
	executionTime := time.Duration(rand.Intn(5)+1) * time.Second
	processed := make(chan int)

	go func() {
		time.Sleep(executionTime)
		processed &lt;- 1
	}()

	select {
	case &lt;-ctx.Done():
		fmt.Printf(&quot;timed out after %s\n&quot;, executionTime)
	case &lt;-processed:
		fmt.Printf(&quot;did something in %s\n&quot;, executionTime)
	}
}

Obs: I changed the original answer a bit. We can not interrupt a goroutine in the middle of its execution. We could delegate the long-running task to another goroutine and receive the result through a dedicated channel.

Warning: I wouldn't recommend that if you expect the processing time to exceed the deadline because now you will have a leaking goroutine.

huangapple
  • 本文由 发表于 2022年6月30日 19:58:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/72815418.html
匿名

发表评论

匿名网友

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

确定