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

rap*_*ock 3 concurrency timeout channel go

当且仅当我在select语句正在侦听的任何通道上没有收到任何信号时,如何在特定时间段内突破包含select语句的惯用Go for循环.

让我以一个例子来提出这个问题.

建立:

  1. 让我们说我有一个var listenCh <-chan string我正在听的频道.
  2. 让我们假设其他一些go例程(不在我们的控制中)在这个通道上发送不同的字符串.
  3. 我用给定的字符串做一些处理,然后监听下一个字符串listenCh.

需求:

listenCh在关闭我的操作(永久地断开for循环)之前,我想在两个连续信号之间等待最多10秒(精度并不严重).

代码存根:

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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我将如何以最有效的方式实现我的要求.

通常的退出通道技术time.After(time.Duration)似乎在一个循环后没有重置,因此即使存在连续的值流,整个程序也会在10秒内关闭.

我在SO上找到了问题的变体(但不是我想要的),但我没有看到我的特定用例.

icz*_*cza 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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

(我在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)
Run Code Online (Sandbox Code Playgroud)

输出(在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
Run Code Online (Sandbox Code Playgroud)

使用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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

测试和输出是一样的.在Go Playground尝试这个.