Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic way for reading from the channel for a certain time

I need to read data from the Go channel for a certain period of time (say 5 seconds). The select statement with timeout doesn't work for me, as I need to read as many values as available and stop exactly after 5 seconds. So far, I've come up with a solution using an extra time channel https://play.golang.org/p/yev9CcvzRIL

package main

import "time"
import "fmt"

func main() {
    // I have no control over dataChan
    dataChan := make(chan string)
    // this is a stub to demonstrate some data coming from dataChan
    go func() {
        for {
            dataChan <- "some data"
            time.Sleep(time.Second)
        }
    }()

    // the following is the code I'm asking about
    timeChan := time.NewTimer(time.Second * 5).C
    for {
        select {
        case d := <-dataChan:
            fmt.Println("Got:", d)
        case <-timeChan:
            fmt.Println("Time's up!")
            return
        }
    }
}

I'm wondering is there any better or more idiomatic way for solving this problem?

like image 954
vitr Avatar asked Apr 17 '18 07:04

vitr


2 Answers

That's pretty much it. But if you don't need to stop or reset the timer, simply use time.After() instead of time.NewTimer(). time.After() is "equivalent to NewTimer(d).C".

afterCh := time.After(5 * time.Second)
for {
    select {
    case d := <-dataChan:
        fmt.Println("Got:", d)
    case <-afterCh:
        fmt.Println("Time's up!")
        return
    }
}

Alternatively (to your liking), you may declare the after channel in the for statement like this:

for afterCh := time.After(5 * time.Second); ; {
    select {
    case d := <-dataChan:
        fmt.Println("Got:", d)
    case <-afterCh:
        fmt.Println("Time's up!")
        return
    }
}

Also I know this is just an example, but always think how a goroutine you start will properly end, as the goroutine producing data in your case will never terminate.

Also don't forget that if multiple cases may be executed without blocking, one is chosen randomly. So if dataChan is ready to receive from "non-stop", there is no guarantee that the loop will terminate immediately after the timeout. In practice this is usually not a problem (starting with that even the timeout is not a guarantee, see details at Golang Timers with 0 length), but you should not forget about it in "mission-critial" applications. For details, see related questions:

force priority of go select statement

golang: How the select worked when multiple channel involved?

like image 137
icza Avatar answered Mar 06 '23 05:03

icza


It looks like context with deadline would be good fit, something like

func main() {
    dataChan := make(chan string)

    ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
    defer cancel()

    go func(ctx context.Context) {
        for {
            select {
            case dataChan <- "some data":
                time.Sleep(time.Second)
            case <-ctx.Done():
                fmt.Println(ctx.Err())
                close(dataChan)
                return
            }
        }
    }(ctx)

    for d := range dataChan {
        fmt.Println("Got:", d)
    }

}
like image 28
ain Avatar answered Mar 06 '23 05:03

ain