英文:
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("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
}
}
}
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 <- 1
}()
select {
case <-ctx.Done():
fmt.Printf("timed out after %s\n", executionTime)
case <-processed:
fmt.Printf("did something in %s\n", 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论