Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For loop that breaks after n amount of seconds

How can I make this simple for loop break after exactly one 1s has passed since its execution?

var i int

for {
  i++
}
like image 202
user3017869 Avatar asked Jan 19 '17 09:01

user3017869


2 Answers

By checking the elapsed time since the start:

var i int
for start := time.Now(); time.Since(start) < time.Second; {
    i++
}

Or using a "timeout" channel, acquired by calling time.After(). Use select to check if time is up, but you must add a default branch so it will be a non-blocking check. If time is up, break from the loop. Also very important to use a label and break from the for loop, else break will just break from the select and it will be an endless loop.

loop:
    for timeout := time.After(time.Second); ; {
        select {
        case <-timeout:
            break loop
        default:
        }
        i++
    }

Note: If the loop body also performs communication operations (like send or receive), using a timeout channel may be the only viable option! (You can list the timeout check and the loop's communication op in the same select.)

We may rewrite the timeout channel solution to not use a label:

for stay, timeout := true, time.After(time.Second); stay; {
    i++
    select {
    case <-timeout:
        stay = false
    default:
    }
}

Optimization

I know your loop is just an example, but if the loop is doing just a tiny bit of work, it is not worth checking the timeout in every iteration. We may rewrite the first solution to check timeout e.g. in every 10 iterations like this:

var i int
for start := time.Now(); ; {
    if i % 10 == 0 {
        if time.Since(start) > time.Second {
            break
        }
    }
    i++
}

We may choose an iteration number which is a multiple of 2, and then we may use bitmasks which is supposed to be even faster than remainder check:

var i int
for start := time.Now(); ; {
    if i&0x0f == 0 { // Check in every 16th iteration
        if time.Since(start) > time.Second {
            break
        }
    }
    i++
}

We may also calculate the end time once (when the loop must end), and then you just have to compare the current time to this:

var i int
for end := time.Now().Add(time.Second); ; {
    if i&0x0f == 0 { // Check in every 16th iteration
        if time.Now().After(end) {
            break
        }
    }
    i++
}
like image 72
icza Avatar answered Oct 12 '22 11:10

icza


I know the question is a bit old, but below might be useful for someone looking for similar scenario:

func keepCheckingSomething() (bool, error) {
    timeout := time.After(10 * time.Second)
    ticker := time.Tick(500 * time.Millisecond)
    // Keep trying until we're timed out or get a result/error
    for {
        select {
        // Got a timeout! fail with a timeout error
        case <-timeout:
            return false, errors.New("timed out")
        // Got a tick, we should check on checkSomething()
        case <-ticker:
            ok, err := checkSomething()
            if err != nil {
            // We may return, or ignore the error
                return false, err
            // checkSomething() done! let's return
            } else if ok {
                return true, nil
            }
            // checkSomething() isn't done yet, but it didn't fail either, let's try again
        }
    }
}
like image 20
Tushar Avatar answered Oct 12 '22 12:10

Tushar