Reputation: 1717
How do I break out of the idiomatic Go for loop containing select statement, if and only if I receive no signals on any of the channels my select statement is listening to, for a particular period of time.
Let me enhance the question with an example.
var listenCh <-chan string
that I am listening to.listenCh
.I want to wait 10 seconds maximum(precision not critical), between two successive signals on the listenCh
, before I shut down my operations(break out of for loop permanently).
func doingSomething(listenCh <-chan string) {
var mystr string
for {
select {
case mystr <-listenCh:
//dosomething
case /*more than 10 seconds since last signal on listenCh*/:
return
}
}
}
How would I achieve my requirement in the most efficient manner possible.
The usual quit channel technique with time.After(time.Duration)
seems to not reset after one loop and hence the whole program closes in 10 seconds even if there is a continuous stream of values.
I find variants of the question(but not what I want) on SO, but none that I saw answers my particular use case.
Upvotes: 4
Views: 1348
Reputation: 417572
Foreword: Using time.Timer
is the recommended way, use of time.After()
here is only for demonstration and reasoning. Please use the 2nd approach.
time.After()
(not recommended for this)If you put time.After()
in the case branch, that will "reset" in each iteration, because that will return you a new channel each time, so that works:
func doingSomething(listenCh <-chan string) {
for {
select {
case mystr := <-listenCh:
log.Println("Received", mystr)
case <-time.After(1 * time.Second):
log.Println("Timeout")
return
}
}
}
(I used 1 second timeout for testability on the Go Playground.)
We can test it like:
ch := make(chan string)
go func() {
for i := 0; i < 3; i++ {
ch <- fmt.Sprint(i)
time.Sleep(500 * time.Millisecond)
}
}()
doingSomething(ch)
Output (try it on the Go Playground):
2009/11/10 23:00:00 Received 0
2009/11/10 23:00:00 Received 1
2009/11/10 23:00:01 Received 2
2009/11/10 23:00:02 Timeout
time.Timer
(recommended solution)If there is a high rate receiving from the channel, this might be a bit of wasting resources, as a new timer is created and used under the hood by time.After()
, which doesn't magically stop and get garbage collected immediately when it's not needed anymore in case you receive a value from the channel before timeout.
A more resource-friendly solution would be to create a time.Timer
before the loop, and reset it if a value is received before a timeout.
This is how it would look like:
func doingSomething(listenCh <-chan string) {
d := 1 * time.Second
t := time.NewTimer(d)
for {
select {
case mystr := <-listenCh:
log.Println("Received", mystr)
if !t.Stop() {
<-t.C
}
t.Reset(d)
case <-t.C:
log.Println("Timeout")
return
}
}
}
Testing and output is the same. Try this one on the Go Playground.
Upvotes: 6