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

Я использую горутины / каналы, чтобы проверить, доступен ли список URL-адресов. Вот мой код. Кажется, это всегда возвращает истину. Почему не выполняется тайм-аут? Цель состоит в том, чтобы вернуть 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"}))
}

person Kamal    schedule 10.05.2014    source источник


Ответы (4)


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, если один из них доступен. Если тайм-аут велик по сравнению со временем, необходимым для запуска горутины, вы можете упростить это, установив всего один тайм-аут для всех 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"}))
}
person Thomas    schedule 10.05.2014
comment
Спасибо. На самом деле, я хочу вернуть недостижимость, если один из них недоступен. Таким образом, функция проверки может записывать false в канал только в том случае, если ему не удается достичь URL-адреса (и ничего не записывать, если он достигает URL-адреса) в течение тайм-аута и времени. AfterFunc может записать true по истечении общего тайм-аута. - person Kamal; 10.05.2014

Причина, по которой это всегда возвращает истину, заключается в том, что вы вызываете 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)) }()
}

Играть

person Sebastian    schedule 10.05.2014

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

ch := make(chan bool, 1)

to

ch := make(chan bool)

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

person ABri    schedule 19.07.2014

Результат, возвращаемый здесь true, является детерминированным в этом сценарии, это не случайный результат, полученный средой выполнения, потому что доступно только истинное значение (сколько бы времени оно ни потребовалось, чтобы оно стало доступным!), Отправляемое в канал, ложный результат никогда не будет доступен для канала с момента вызова time.After () никогда не получит шанс быть выполнен в первую очередь!

В этом select первая исполняемая строка, которую он видит, это вызов check (u), а не канал, отправляющий вызов в первой ветви case, или любой другой вызов вообще! И только после того, как это первое выполнение check (u) вернется сюда, выберет варианты ветвления, которые будут проверены и вызваны, к этому моменту значение true уже будет помещено в первый канал варианта ветвления, поэтому здесь нет блокировки канала к оператору select, здесь select может быстро выполнить свою задачу без необходимости проверять оставшиеся варианты ветвления!

так что похоже, что использование здесь select было бы не совсем правильным в этом сценарии.

Предполагается, что выбранные варианты ветвления непосредственно прослушивают отправку и получение значений канала или, необязательно, со значением по умолчанию, чтобы избежать блокировки, когда это необходимо.

так что исправление, как уже указывали здесь некоторые люди, помещает длительную задачу или процесс в отдельную горутину и отправляет результат в канал, а затем в основную горутину (или любую другую процедуру, которая требует это значение вне канала ), используйте варианты ветвления select для прослушивания значения на этом конкретном канале или на канале, предоставленном вызовом time.After (time.Second).

По сути, эта строка: case ch ‹- check (u) верна в смысле отправки значения в канал, но это просто не для его предполагаемого использования (т.е. блокирование этого случая ветвления), потому что case channel‹ - не быть заблокированным там вообще (время, затрачиваемое на проверку (u), происходит до того, как будет задействован канал), поскольку в отдельной горутине, также известной как основная: return ‹-ch, она уже готова прочитать это значение всякий раз, когда оно проталкивается. Вот почему оператор вызова time.After () во второй ветке case никогда даже не получит шанса быть вычисленным в первом экземпляре!

см. этот пример для простого решения, т.е. правильное использование select в сочетании с отдельными горутинами: https://gobyexample.com/timeouts

person xman    schedule 31.08.2019