当选择组中的任何通道在指定时间内没有收到信号时,中断跳出环路

时间:2018-11-09 02:32:49

标签: go concurrency timeout channel

当且仅当在特定时间段内,在我的select语句正在侦听的任何通道上未收到任何信号的情况下,如何打破包含select语句的惯用Go for循环。

让我用一个例子来说明这个问题。

设置:

  1. 可以说我有一个正在收听的频道var listenCh <-chan string
  2. 让我们假设其他一些go例程(不在我们的控件中)在此通道上发送不同的字符串。
  3. 我对给定的字符串进行了一些处理,然后在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上找到了这个问题的变体(但不是我想要的),但是我看不到任何答案可以满足我的特定用例。

1 个答案:

答案 0 :(得分:5)

前言::建议使用time.Timer,此处使用time.After()仅用于演示和推理。请使用第二种方法。


使用time.After()(不建议这样做)

如果将time.After()放在case分支中,它将在每次迭代中“重置”,因为每次都会返回一个新通道,因此可以正常工作:

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上尝试这个。