golang с использованием таймаутов с каналами

Я использую goroutines / каналы, чтобы проверить, доступен ли список URL-адресов. Вот мой код. Это, кажется, всегда возвращать true. Почему дело timeout не выполняется? Цель состоит в том, чтобы вернуть false, даже если один из URL-адресов недоступен

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"}))
}

3 ответов


check(u) будет спать в настоящее goroutine, то есть тот, который работает func. The 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, если один из них доступен. Если timeout долго по сравнению со временем, которое требуется для вращения 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, заключается в том, что вы вызываете 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
    }
}

затем вызовите эту функцию из a петля:

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

играть


изменить строку

ch := make(chan bool, 1)

to

ch := make(chan bool)

вы открыли асинхронный (=неблокирующий) канал, но вам нужен блокирующий канал, чтобы заставить его работать.