我正在使用gooutines/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"}))
}
check(u)
将在当前例程中休眠,即运行func
的例程。select
语句只有在它返回时才正常运行,到那时,两个分支都是可运行的,运行时可以选择它喜欢的任何一个。
您可以通过在另一个例程中运行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"}))
}
这总是返回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)) }()
}
玩改变行
ch := make(chan bool, 1)
ch := make(chan bool)
您确实打开了一个异步(=非阻塞)通道,但您需要一个阻塞通道才能使其工作
这里返回的true结果在此场景中是确定的,它不是运行时随机选取的结果,因为只有true值可用(无论它需要多长时间才能变得可用!)被发送到通道中,false结果将永远不会在通道中可用。after()调用语句将永远不会有机会首先执行!
在这个select中,它看到的第一个可执行行是check(u)调用,而不是第一个case分支中的通道发送调用,或者任何其他调用!只有在第一次检查(u)执行返回之后,才会检查并调用选择分支用例,此时,true的值已经被推入第一个分支用例通道,所以这里没有通道阻塞到select语句,选择可以在这里迅速完成其目的,而无需检查剩余的分支用例!
所以看起来这里使用select在这个场景中似乎不太正确。
选择分支情况应该直接侦听通道发送和接收值,或者在必要时可选择使用默认值来逃避阻塞。
所以修复是一些人在这里已经指出,把长时间运行的任务或进程到一个单独的程序,并让它发送结果到通道,然后在主例程(或任何其他需要该通道值的例程)中,使用select分支用例侦听该特定通道上的值,或者侦听time.After(time.Second)调用提供的通道。
基本上,这行:case ch <- check(u)在将值发送到通道的意义上是正确的,但它只是不适合其预期用途(即阻塞该分支情况),因为case channel<-根本没有被阻塞(check(u)花费的时间都发生在通道参与之前),因为在一个单独的例程中,也就是主例程:return <-ch,它已经准备好读取该值了。这就是为什么在第二种情况下time.After()调用语句的分支在第一个实例中甚至没有机会被求值!
查看这个示例的简单解决方案,即。在单独的程序中正确使用select: https://gobyexample.com/timeouts
如果它有用,这里是@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
}
然后你可以调用'timeout'任何函数
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{...