简体   繁体   中英

measure execution time and stop waiting if too long in golang

I am trying to measure execution time of funcWithUnpredictiveExecutionTime function.

func measureTime(expectedMs float64) (ok bool) {
    t1 := time.Now()
    funcWithUnpredictiveExecutionTime()
    t2 := time.Now()
    diff := t2.Sub(t1)

The measuring is fine when funcWithUnpredictiveExecutionTime works faster than I expected. But if it works slower than expectedMs the measuring will not stop right after expected amount of milliseconds passed.

Is it possible to stop time measuring when funcWithUnpredictiveExecutionTime works longer than expectedMs without waiting funcWithUnpredictiveExecutionTime to finish?

In other words, measureTime(200) should return in 200 ms anyway with a good or bad result.

I guess I should use channels and then somehow cancel waiting for a channel. But how to do it exactly?

Full code:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

// random number between min and max
func random(min, max int) int {
    rand.Seed(time.Now().Unix())
    return rand.Intn(max-min) + min
}

// sleeps for a random milliseconds amount between 200 and 1000
func funcWithUnpredictiveExecutionTime() {
    millisToSleep := random(200, 1000)
    fmt.Println(fmt.Sprintf("Sleeping for %d milliseconds", millisToSleep))
    time.Sleep(time.Millisecond * time.Duration(millisToSleep))
}

// measures execution time of a function funcWithUnpredictiveExecutionTime
// if expectedMs < actual execution time, it's ok.
// if expectedMs milliseconds passed and funcWithUnpredictiveExecutionTime
// still did not finish execution it should return
// without waiting for funcWithUnpredictiveExecutionTime
func measureTime(expectedMs float64) (ok bool) {
    t1 := time.Now()
    funcWithUnpredictiveExecutionTime()
    t2 := time.Now()
    diff := t2.Sub(t1)
    actualMs := diff.Seconds() * 1000
    ok = actualMs < expectedMs
    fmt.Println(actualMs)
    return
}

// prints results: Ok or too late
func printTimeResults(ok bool) {
    if ok {
        fmt.Println("Ok")
    } else {
        fmt.Println("Too late")
    }
}

func main() {
    printTimeResults(measureTime(200))  // expect it to finish in 200 ms anyway
    printTimeResults(measureTime(1000)) // expect it to finish in 1000 ms anyway
}

Output:

Sleeping for 422 milliseconds
424.11895200000004
Too late
Sleeping for 422 milliseconds
425.27274900000003
Ok

Playground

You can't cancel a goroutine, unless you design it to be canceled. You can short circuit your timing function, by using a channel to signal the completion of the function being timed:

func measureTime(expectedMs float64) (ok bool) {
    done := make(chan struct{})
    t1 := time.Now()

    go func() {
        funcWithUnpredictiveExecutionTime()
        close(done)
    }()

    select {
    case <-done:
        ok = true
    case <-time.After(time.Duration(expectedMs) * time.Millisecond):
    }

    fmt.Println(time.Since(t1))
    return ok

}

Extending JimB's example a little with the design I've personally followed for async background workers. I would say in most cases it's unacceptable to launch a go routine without passing an abort channel... All of your async methods should accept one as an argument or have one defined on their receiving type so you can actually control execution. fyi there are libraries for this, here's a simple one an old colleague of mine made; https://github.com/lytics/squaredance

If your program does not have an abort path for every goroutine you're probably going to face significant quality issues sooner or later. Also, for applications that are doing any heavy lifting in a goroutine, you will likely not be able to gracefully stop and start your application.

func measureTime(expectedMs float64) (ok bool) {
    done := make(chan struct{})
    abort := make(chan struct{})
    t1 := time.Now()

    go func() {
        funcWithUnpredictiveExecutionTime(abort)
        close(done)
    }()

    select {
    case <-done:
        ok = true
    case <-time.After(time.Duration(expectedMs) * time.Millisecond):
        // after signals here when the duration is reached so I close abort
        close(abort)
    }

    fmt.Println(time.Since(t1))
    return ok

}


funcWithUnpredictiveExecutionTime(abort) {
     for {
        select {
           // doing work in here
           case abort:
              // except here, we clean up and return
        }
     }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM