【问题标题】:Go using timeouts with channels使用通道超时
【发布时间】:2014-06-28 05:28:57
【问题描述】:

我正在使用 goroutines/channels 来检查 url 列表是否可以访问。这是我的代码。这似乎总是返回 true。为什么超时情况没有被执行?目标是即使其中一个 url 不可达也返回 false

import "fmt"
import "time"

func check(u string) bool {
    time.Sleep(4 * time.Second)
    return true
}

func IsReachable(urls []string) bool {

    ch := make(chan bool, 1)
    for _, url := range urls {
        go func(u string) {
            select {
            case ch <- check(u):
            case <-time.After(time.Second):
                ch<-false
            }
        }(url)
    }
    return <-ch
}
func main() {
    fmt.Println(IsReachable([]string{"url1"}))
}

【问题讨论】:

    标签: go channels


    【解决方案1】:

    总是返回 true 的原因是您在 select 语句中调用了 check(u)。您需要在 go 例程中调用它,然后使用 select 等待结果或超时。

    如果您想并行检查多个 URL 的可访问性,则需要重新构建代码。

    首先创建一个函数来检查一个 URL 的可达性:

    func IsReachable(url string) bool {
        ch := make(chan bool, 1)
        go func() { ch <- check(url) }()
        select {
        case reachable := <-ch:
            return reachable
        case <-time.After(time.Second):
            // call timed out
            return false
        }
    }
    

    然后从循环中调用这个函数:

    urls := []string{"url1", "url2", "url3"}
    for _, url := range urls {
        go func() { fmt.Println(IsReachable(url)) }()
    }
    

    Play

    【讨论】:

      【解决方案2】:

      check(u) 将在 current goroutine 中休眠,即运行 func 的那个。 select 语句只有在返回后才能正常运行,到那时,两个分支都可以运行,运行时可以选择它喜欢的任何一个。

      您可以通过在另一个 goroutine 中运行 check 来解决它:

      package main
      
      import "fmt"
      import "time"
      
      func check(u string, checked chan<- bool) {
          time.Sleep(4 * time.Second)
          checked <- true
      }
      
      func IsReachable(urls []string) bool {
      
          ch := make(chan bool, 1)
          for _, url := range urls {
              go func(u string) {
                  checked := make(chan bool)
                  go check(u, checked)
                  select {
                  case ret := <-checked:
                      ch <- ret
                  case <-time.After(1 * time.Second):
                      ch <- false
                  }
              }(url)
          }
          return <-ch
      }
      func main() {
          fmt.Println(IsReachable([]string{"url1"}))
      }
      

      您似乎想检查一组 URL 的可访问性,如果其中一个可用则返回 true。如果与启动 goroutine 所需的时间相比,超时时间较长,则可以通过为所有 URL 一起设置一个超时来简化此操作。但是我们需要确保通道足够大以容纳所有检查的答案,否则那些没有“获胜”的将永远阻塞:

      package main
      
      import "fmt"
      import "time"
      
      func check(u string, ch chan<- bool) {
          time.Sleep(4 * time.Second)
          ch <- true
      }
      
      func IsReachable(urls []string) bool {
          ch := make(chan bool, len(urls))
          for _, url := range urls {
              go check(url, ch)
          }
          time.AfterFunc(time.Second, func() { ch <- false })
          return <-ch
      }
      func main() {
          fmt.Println(IsReachable([]string{"url1", "url2"}))
      }
      

      【讨论】:

      • 谢谢。实际上,如果其中一个不可达,我想返回不可达。所以check函数只有在超时时间和时间内没有到达url(到达url时什么也不写)才可以向channel写入“false”。AfterFunc在总超时后可以写入true。
      【解决方案3】:

      换行

      ch := make(chan bool, 1)
      

      ch := make(chan bool)
      

      您确实打开了一个异步(= 非阻塞)通道,但您需要一个阻塞通道才能使其工作。

      【讨论】:

        【解决方案4】:

        在这种情况下,此处返回的 true 的结果是确定性的,它不是运行时获取的随机结果,因为只有可用的 true 值(无论它变得可用需要多长时间!)被发送到通道,从 time.After() 调用语句开始就永远不会有机会执行该通道!

        在这个选择中,它看到的第一个可执行行是 check(u) 调用,而不是第一个 case 分支中发送调用的通道,或者根本没有任何其他调用!并且只有在第一个 check(u) 执行返回到这里之后,才会检查并调用选择分支案例,此时,true 的值已经被推送到第一个分支案例通道,所以这里没有通道阻塞对select语句来说,select可以在这里迅速完成其目的,而不需要检查其剩余的分支案例!

        所以看起来这里使用 select 在这种情况下似乎不太正确。

        选择分支案例应该直接监听通道发送和接收值,或者在必要时可以选择使用默认值来逃避阻塞。

        所以修复就像一些人已经在这里指出的那样,将长时间运行的任务或进程放入一个单独的 goroutine,并将结果发送到通道, 然后在主 goroutine(或任何其他需要该值离开通道的例程)中,使用选择分支案例在该特定通道上侦听值,或在 time.After(time.Second ) 调用。

        基本上,这一行:case ch

        查看此示例以获得简单的解决方案,即。正确使用 select 与单独的 goroutines 结合使用:https://gobyexample.com/timeouts

        【讨论】:

          【解决方案5】:

          如果有用,这里是@Thomas 答案的通用版本,@mh-cbon 大大简化了

          func WithTimeout(delegate func() interface{}, timeout time.Duration) (ret interface{}, ok bool) {
              ch := make(chan interface{}, 1) // buffered
              go func() { ch <- delegate() }()
              select {
              case ret = <-ch:
                  return ret, true
              case <-time.After(timeout):
              }
              return nil, false
          }
          

          然后你可以调用任何函数来“超时”

          if value,ok := WithTimeout(myFunc, time.Second); ok {
              // returned
          } else {
              // didn't return
          }
          

          这样调用等待频道

          if value,ok := WithTimeout(func()interface{}{return <- inbox}, time.Second); ok {
              // returned
          } else {
              // didn't return
          }
          

          像这样尝试发送

          _,ok = WithTimeout(func()interface{}{outbox <- myValue; return nil}, time.Second)
              if !ok{...
          

          【讨论】:

          • 反馈应该被缓冲,否则如果发生超时,你会泄漏例程。如果超时选择是在调用站点上,而不是在被调用者上,则根本不需要反馈通道。 play.golang.org/p/7OuRcKaC5Hc
          • 谢谢@mh-cbon,这更简单(也适用于接受的答案)
          猜你喜欢
          • 2021-05-17
          • 1970-01-01
          • 2016-10-15
          • 2018-10-04
          • 2011-06-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多