golang使用频道超时

时间:2014-05-10 14:19:59

标签: go channels

我正在使用goroutines / channels来检查网址列表是否可以访问。这是我的代码。这似乎总是回归真实。为什么超时案例没有被执行?即使其中一个网址无法访问,目标也是返回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"}))
}

4 个答案:

答案 0 :(得分:12)

check(u)将在当前 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"}))
}

您似乎想要检查一组网址的可访问性,如果其中一个网址可用,则返回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"}))
}

答案 1 :(得分:4)

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

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

首先创建一个检查一个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 :(得分:1)

更改行

ch := make(chan bool, 1)

ch := make(chan bool)

你确实打开了一个异步(=非阻塞)频道,但你需要一个阻止频道才能让它发挥作用。

答案 3 :(得分:0)

在这种情况下,返回true的结果是确定性的,它不是运行时选择的随机值,因为只有真实值可用(但是可能需要多长时间才能变为可用!)通道,自该时间以来,错误的结果将永远不再可用。after()调用语句永远不会有机会被执行!

在此选择中,它看到的第一个可执行行是check(u)调用,而不是第一个case分支中的通道发送调用,或任何其他调用!而且只有在此第一个check(u)执行返回这里之后,才会选择并调用选择的分支案例,此时,true的值已经被推送到第一个分支案例通道中,因此这里没有通道阻塞对于select语句,select可以在这里立即实现其目的,而无需检查其剩余分支情况!

因此,在这种情况下,似乎使用select似乎不太正确。

select分支的情况应该直接侦听通道的发送和接收值,或者可选地使用默认值以在必要时逃避阻塞。

因此,正如某些人已经指出的那样,此修复程序将长期运行的任务或流程放入单独的goroutine中,并将结果发送到通道中, 然后在主goroutine(或需要从通道中获取该值的其他任何例程)中,使用select分支案例在该特定通道上侦听某个值,或者在time.After(time.Second )通话。

基本上,这行代码是:case ch <-check(u)在将值发送到通道的意义上是正确的,但是它不是用于预期的用途(即阻塞此分支case),因为case channel < -根本没有被阻塞(check(u)花费的时间全部在涉及通道之前发生),因为在一个单独的goroutine中,又是主要的goroutine:return <-ch,它已经准备好读取随时获得价值。这就是为什么在第二个case分支中的time.After()调用语句在第一个实例中甚至都没有机会被评估的原因!

有关简单的解决方案,请参见此示例,即。正确使用select与单独的goroutine结合使用:https://gobyexample.com/timeouts