23

我正在使用 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"}))
}
4

5 回答 5

22

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

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

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"}))
}
于 2014-05-10T14:47:51.340 回答
6

这总是返回 true 的原因是您check(u)select语句中调用。您需要在 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)) }()
}

于 2014-05-10T14:44:10.097 回答
1

换行

ch := make(chan bool, 1)

ch := make(chan bool)

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

于 2014-07-19T15:33:06.727 回答
0

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

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

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

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

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

基本上,这一行: case ch <- check(u) 在将值发送到通道的意义上是正确的,但它只是不用于其预期用途(即阻止此分支案例),因为 case channel<- 不是完全被阻塞在那里(检查(u)花费的时间都发生在通道参与之前),因为在一个单独的goroutine中,也就是主要的:return <-ch,它已经准备好读取该值被推过去。这就是为什么第二个 case 分支中的 time.After() 调用语句在第一个实例中甚至没有机会被评估!

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

于 2019-08-31T11:58:46.630 回答
0

如果它有用,这是@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{...
于 2021-08-26T10:04:21.387 回答