DEV Community

Julian-Chu
Julian-Chu

Posted on

[Go] for-select with timer

#go

"for-select" is very important in golang for non-blocking operation. "Timer" is a good partner with for-select to handle timeout and performance issue.

In this post, I show some use cases, how for-select with timer work together.

Timeout

Listen to 2 channels, if no receive from any channel in 1s, it goes to the timeout case

    for {
        select {
        case a := <-ch1:
            fmt.Println(a)
        case b := <-ch2:
            fmt.Println(b)
        case <-time.After(time.Second):
                // timeout, do something
        }
      }

Interval

Sometimes we can see new gophers write the following code:

    for {
        select {
        case a := <-ch1:
            fmt.Println(a)
        case b := <-ch2:
            fmt.Println(b)
        default:
                // always non-block here, 
        }
             // no interval, run this for-loop intensively
      }

Then most of usage CPU maybe occupied because of intensive non-blocking infinite loop. We could use timeout case to avoid it, or add interval to entire for loop like this.

    for {
        select {
        case a := <-ch1:
            fmt.Println(a)
        case b := <-ch2:
            fmt.Println(b)
        default:                     
        }
        <-time.After(time.Second) // or time.sleep(time.Second)
      }

The for loop now has 1s interval.

Timer

Sometimes we would like to set timer to break for-loop.
You may implement like following code:

    t := time.NewTimer(time.Second * 5)
    loop:
        for {
            select {
            case a := <-ch1:
                fmt.Println(a)
            case b := <-ch2:
                fmt.Println(b)
            case <-time.After(time.Millisecond *500):
            case <-t.C:
                break loop
            }
        }

The code works, but there's a potential issue in this implementation. Be careful, "select" statement chooses any non-blocked case randomly, for example, even if the timer is fired, select can pick up another cases when they're non-blocked.
So we can improve it by adding another select statement.

    t := time.NewTimer(time.Second * 5)
    loop:
        for {                        
            select {
            case <-t.C:  // if timer is fired, break the loop 
                break loop
            default:    // timer is not fired, non-blocked 
            }

            select {
            case a := <-ch1:
                fmt.Println(a)
            case b := <-ch2:
                fmt.Println(b)
            case <-time.After(time.Millisecond *500):           
            }

    }

I think these are common use cases for for-select with Timer.
If any mistake and question, please feel free to write comments.

Top comments (0)