如何解决超时后退出 goroutine
我正在尝试在 go 中编写一个类似于 cron 的程序,另外还为作业提供了最大运行时间,如果函数超过此持续时间,作业应该退出。这是我的全部代码:
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.MaxRuntime)
// here should exit the above goroutine
}
}
}
func main() {
// create a new job given its name,max runtime
// and the function it should run
testJob := New("my-first-job","3s","5s",func() {
testFunc()
})
testJob.Run()
}
我想要做的是,在选择 Run() 函数的第二种情况下,它应该退出运行该函数的 goroutine。我试图通过将函数包装在一个 for 循环中,并使用一个 select 语句来完成此操作,该语句在这样的退出通道上进行侦听:
go func() {
for {
select {
case <-quit:
fmt.Println("quiting goroutine")
return
default:
j.Function()
done <- 0
}
}
}()
然后在 Run() 函数中有 quit <- 1
,但这似乎没有做任何事情。这样做有没有更好的办法?
解决方法
正如评论中所解释的,整个问题是您想要取消一个不可取消的函数 (j.Function
) 的执行。
没有办法“杀死一个goroutine”。 Goroutines 以协作的方式工作。如果你想能够“杀死它”,你需要确保在那个 Goroutine 中运行的函数有一个机制让你发出信号,它应该停止它正在做的事情并返回,让运行它的 Goroutine 最终终止。
指示函数可取消的标准方法是让它接受一个 context.Context
作为它的第一个参数:
type Job struct {
// ...
Function func(context.Context)
}
然后创建上下文并将其传递给 j.Function
。由于您的取消逻辑仅基于超时,因此无需编写所有 select ... case <-time.After(...)
,因为它是作为带有 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)
}
现在,为了完成,您必须重写将要传递给作业调度程序的函数,以便它们使用 context.Context
,而且非常重要的是,它们使用当上下文被取消时,它会正确地取消他们正在做的任何事情。
这意味着,如果您正在为这些函数编写代码并且它们会以某种方式阻塞,那么您将负责编写以下内容:
select {
case <-ctx.Done():
return ctx.Err()
case ...your blocking case...:
}
如果您的 funcs 正在调用 3rd 方代码,那么 那个 代码需要了解上下文和取消,并且您需要传递您的 funcs 收到的 ctx。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。