Go 如何强制中断在预定时间内花费太长时间的函数执行

Go 如何强制中断在预定时间内花费太长时间的函数执行,go,scheduled-tasks,kill,preemption,Go,Scheduled Tasks,Kill,Preemption,我已经编写了这个调度程序,但是当f占用的时间超过输入循环时间间隔时,我无法使它终止输入函数f 如果f是进程而不是线程,那么我要寻找的东西可能是某种定义的硬抢占 f的定义是我无法控制的。它表示一个ETL作业,涉及在批处理执行期间处理来自多个数据库的数据。它是用围棋写的,运行得很好,但我需要对它进行某种控制,因为执行时间太长了 我知道f是原子的,所以它要么在执行结束时更改数据库,要么不更改。因此,如果时间过长,可以认为杀死它是安全的 func schedule(f func(), recurring

我已经编写了这个调度程序,但是当f占用的时间超过输入循环时间间隔时,我无法使它终止输入函数f

如果f是进程而不是线程,那么我要寻找的东西可能是某种定义的硬抢占

f的定义是我无法控制的。它表示一个ETL作业,涉及在批处理执行期间处理来自多个数据库的数据。它是用围棋写的,运行得很好,但我需要对它进行某种控制,因为执行时间太长了

我知道f是原子的,所以它要么在执行结束时更改数据库,要么不更改。因此,如果时间过长,可以认为杀死它是安全的

func schedule(f func(), recurring time.Duration) chan struct{} {
    ticker := time.NewTicker(recurring)
    quit := make(chan struct{})
    go func(inFunc func()) {
        for {
            select {
            case <-ticker.C:
                fmt.Println("Ticked")
                // when "go" is removed, then if "f()" takes
                // more than "recurring", then it postpones
                // the following executions of "f()"
                //
                // instead somehow it should be "killed"
                // 
                // check the timestamps in the execution of the test
                go inFunc()
            case <-quit:
                fmt.Println("Stopping the scheduler")
                ticker.Stop()
                return
            }
        }
    }(f)

    return quit
}

我与f的作者协商使用超时上下文

请参阅下面的一个工作示例,注意dummyLog的时间戳,因此应该清楚该过程中涉及的所有go例程都发生了什么

守则:

// dummyLog could be used to log some information using a human readable timestamp and the benefits of `fmt.Sprintf`
func dummyLog(format string, a ...interface{}) (n int, err error) {
    prefix := fmt.Sprintf("[%v] ", time.Now())
    message := fmt.Sprintf(format, a...)
    return fmt.Printf("%s%s\n", prefix, message)
}

// newContext is providing a brand new context with a upper bound timeout
func newContext(timeoutUpperBound time.Duration) (context.Context, context.CancelFunc) {
    ctx, cancel := context.WithTimeout(context.Background(), timeoutUpperBound)
    deadline, ok := ctx.Deadline()
    dummyLog("The context deadline is set to %s is it still valid? %v", deadline, ok)
    return ctx, cancel
}

// schedule could be used to schedule arbitrary functions with a recurring interval
func schedule(f func(ctx context.Context), recurring time.Duration) chan struct{} {
    ticker := time.NewTicker(recurring)
    quit := make(chan struct{})
    go func(inFunc func(ctx context.Context)) {
        for {
            select {
            case <-ticker.C:
                dummyLog("Ticked in the scheduler")
                // simulate the "killing" of "inFunc" when it takes too long
                go func(recurring time.Duration) {
                    inCtx, cancel := newContext(recurring)
                    defer cancel()
                    inFunc(inCtx)
                }(recurring)
            case <-quit:
                dummyLog("Stopping the scheduler")
                ticker.Stop()
                return
            }
        }
    }(f)

    return quit
}
在测试环境中执行代码,尽管未执行断言:

func TestSomething(t *testing.T) {

    // newUuid could be used to generate a UUID to be able to uniquely identify "fooFunc"
    newUuid := func() string {
        // sudo apt-get install uuid-runtime
        uuid, _ := exec.Command("uuidgen").Output()

        re := regexp.MustCompile(`\r?\n`)
        uuidStr := re.ReplaceAllString(string(uuid), "")
        return uuidStr
    }

    // randBetween is a dummy random int generator using "math/rand"
    randBetween := func(min int, max int) int {
        return min + rand.Intn(max-min)
    }

    // fooFunc simulates some sort of very slow execution
    // like database queries or network I/O
    fooFunc := func(ctx context.Context) {
        uuid := newUuid()
        randWait := time.Duration(randBetween(0, 4000)) * time.Millisecond
        dummyLog("Starting task %s taking %s random time", uuid, randWait)
        select {
        case <-time.After(randWait):
            dummyLog("Finished task %s", uuid)
        case <-ctx.Done():
            dummyLog("Killed task %s, reason: '%s'", uuid, ctx.Err())
        }
    }

    // test the very slow execution of "fooFunc"
    timeoutUpperBound := 2 * time.Second
    quitChan := schedule(fooFunc, timeoutUpperBound)

    time.Sleep(6 * timeoutUpperBound)
    close(quitChan)
    // wait more to see the "closing" message
    time.Sleep(4 * time.Second)
}

你不能杀死一个函数或goroutine,你只能让它周期性地检查变量、通道等。。。并在一定条件下退出。您的案例@ MARC,我提供了一些关于F的更多细节,我基本上没有控制它的内部。如果你不能真正改变F,另一个选择是在GOODUTE中运行它,如果你每次只需要一个,那么在超时之后,它会被调度程序运行。认为它死了,让另一个运行,忽略任何东西,它输出的截止日期后。我不知道你想做什么,所以这可能也不合适。假设f有一个清晰的输出,你可以丢弃它,并且没有副作用。如果你不能控制f的内部,你就不能中断f。时期可中断的函数总是需要通过上下文、通道、超时或截止日期提供一种注入该控件的方法。我不确定为什么人们在没有提供反馈的情况下否决了这个问题,在我看来,它以我寻求帮助和建议的方式得到了很好的解释。顺便说一句@JimB谢谢你的建议,我正试图让f的作者同意对其执行的某种控制。如果您有一些最佳实践建议在这种情况下哪种控制更好,那么请让我知道。谢谢
func TestSomething(t *testing.T) {

    // newUuid could be used to generate a UUID to be able to uniquely identify "fooFunc"
    newUuid := func() string {
        // sudo apt-get install uuid-runtime
        uuid, _ := exec.Command("uuidgen").Output()

        re := regexp.MustCompile(`\r?\n`)
        uuidStr := re.ReplaceAllString(string(uuid), "")
        return uuidStr
    }

    // randBetween is a dummy random int generator using "math/rand"
    randBetween := func(min int, max int) int {
        return min + rand.Intn(max-min)
    }

    // fooFunc simulates some sort of very slow execution
    // like database queries or network I/O
    fooFunc := func(ctx context.Context) {
        uuid := newUuid()
        randWait := time.Duration(randBetween(0, 4000)) * time.Millisecond
        dummyLog("Starting task %s taking %s random time", uuid, randWait)
        select {
        case <-time.After(randWait):
            dummyLog("Finished task %s", uuid)
        case <-ctx.Done():
            dummyLog("Killed task %s, reason: '%s'", uuid, ctx.Err())
        }
    }

    // test the very slow execution of "fooFunc"
    timeoutUpperBound := 2 * time.Second
    quitChan := schedule(fooFunc, timeoutUpperBound)

    time.Sleep(6 * timeoutUpperBound)
    close(quitChan)
    // wait more to see the "closing" message
    time.Sleep(4 * time.Second)
}