For loop that breaks after n amount of seconds

Issue

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

var i int

for {
  i++
}

Solution

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++
}

Answered By – icza

Answer Checked By – Clifford M. (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.