select组中指定时间内没有频道收到信号时跳出循环
Break out of loop when no channel in a select group receives signal in specified time
当且仅当我在 select 语句正在收听的任何频道上都没有收到任何信号时,我该如何跳出包含 select 语句的惯用 Go for 循环,对于一个特定时期。
让我用一个例子来加强这个问题。
设置:
- 假设我有一个正在收听的频道
var listenCh <-chan string
。
- 让我们假设一些其他的 go 例程(不在我们的控制范围内)在此通道上发送不同的字符串。
- 我对给定的字符串进行一些处理,然后在
listenCh
上监听下一个字符串。
要求:
我想在 listenCh
上的两个连续信号之间最多等待 10 秒(精度不重要),然后再关闭我的操作(永久中断 for 循环)。
代码存根:
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
}
}
}
我将如何以最有效的方式实现我的要求。
通常使用 time.After(time.Duration)
的退出通道技术似乎不会在一个循环后重置,因此即使有连续的值流,整个程序也会在 10 秒内关闭。
我在 SO 上找到了问题的变体(但不是我想要的),但是我看到的 none 回答了我的特定用例。
前言:推荐使用time.Timer
,此处使用time.After()
仅用于演示和推理。请使用第二种方法。
使用 time.After()
(不推荐这样做)
如果你把 time.After()
放在 case 分支中,那将在每次迭代中 "reset",因为每次都会 return 你一个新的频道,这样就可以了:
func doingSomething(listenCh <-chan string) {
for {
select {
case mystr := <-listenCh:
log.Println("Received", mystr)
case <-time.After(1 * time.Second):
log.Println("Timeout")
return
}
}
}
(我在 Go Playground 上使用了 1 秒的可测试性超时。)
我们可以这样测试:
ch := make(chan string)
go func() {
for i := 0; i < 3; i++ {
ch <- fmt.Sprint(i)
time.Sleep(500 * time.Millisecond)
}
}()
doingSomething(ch)
输出(在 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
(推荐的解决方案)
如果从频道接收的速率很高,这可能会有点浪费资源,因为 time.After()
在后台创建并使用了一个新的计时器,它不会神奇地停止并且如果您在超时前从通道收到值,则在不再需要时立即收集垃圾。
一种资源更友好的解决方案是在循环之前创建一个 time.Timer
,如果在超时之前收到一个值,则将其重置。
这是它的样子:
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
}
}
}
测试和输出是一样的。在 Go Playground.
上试试这个
当且仅当我在 select 语句正在收听的任何频道上都没有收到任何信号时,我该如何跳出包含 select 语句的惯用 Go for 循环,对于一个特定时期。
让我用一个例子来加强这个问题。
设置:
- 假设我有一个正在收听的频道
var listenCh <-chan string
。 - 让我们假设一些其他的 go 例程(不在我们的控制范围内)在此通道上发送不同的字符串。
- 我对给定的字符串进行一些处理,然后在
listenCh
上监听下一个字符串。
要求:
我想在 listenCh
上的两个连续信号之间最多等待 10 秒(精度不重要),然后再关闭我的操作(永久中断 for 循环)。
代码存根:
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
}
}
}
我将如何以最有效的方式实现我的要求。
通常使用 time.After(time.Duration)
的退出通道技术似乎不会在一个循环后重置,因此即使有连续的值流,整个程序也会在 10 秒内关闭。
我在 SO 上找到了问题的变体(但不是我想要的),但是我看到的 none 回答了我的特定用例。
前言:推荐使用time.Timer
,此处使用time.After()
仅用于演示和推理。请使用第二种方法。
使用 time.After()
(不推荐这样做)
如果你把 time.After()
放在 case 分支中,那将在每次迭代中 "reset",因为每次都会 return 你一个新的频道,这样就可以了:
func doingSomething(listenCh <-chan string) {
for {
select {
case mystr := <-listenCh:
log.Println("Received", mystr)
case <-time.After(1 * time.Second):
log.Println("Timeout")
return
}
}
}
(我在 Go Playground 上使用了 1 秒的可测试性超时。)
我们可以这样测试:
ch := make(chan string)
go func() {
for i := 0; i < 3; i++ {
ch <- fmt.Sprint(i)
time.Sleep(500 * time.Millisecond)
}
}()
doingSomething(ch)
输出(在 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
(推荐的解决方案)
如果从频道接收的速率很高,这可能会有点浪费资源,因为 time.After()
在后台创建并使用了一个新的计时器,它不会神奇地停止并且如果您在超时前从通道收到值,则在不再需要时立即收集垃圾。
一种资源更友好的解决方案是在循环之前创建一个 time.Timer
,如果在超时之前收到一个值,则将其重置。
这是它的样子:
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
}
}
}
测试和输出是一样的。在 Go Playground.
上试试这个