Golang, используя таймауты с каналами
Я использую goroutines/channels, чтобы проверить, доступен ли список 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"}))
}
Ответы
Ответ 1
check(u)
будет спать в текущем 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"}))
}
Ответ 2
Причина, по которой это всегда возвращает 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)) }()
}
Play
Ответ 3
измените строку
ch := make(chan bool, 1)
to
ch := make(chan bool)
Вы открыли асинхронный (= неблокирующий) канал, но вам нужен канал блокировки, чтобы заставить его работать.
Ответ 4
Результат возврата истинного значения здесь является детерминированным в этом сценарии, а не случайным, полученным во время выполнения, потому что в канал отправляется только истинное значение (сколь бы долго оно не становилось доступным!), ложный результат никогда не будет доступен для канала, поскольку оператор вызова time.After() никогда не получит шанс быть выполненным в первую очередь!
В этом выборе первой исполняемой строкой, которую он видит, является вызов check (u), а не канал, отправляющий вызов в первом ответвлении, или любой другой вызов вообще! И только после того, как это первое выполнение check (u) вернулось сюда, будет выбран и проверен выбор вариантов ветвления, после чего значение true уже должно быть передано в канал первого варианта ветвления, так что здесь нет блокировки каналов к утверждению select, select может выполнить свое назначение здесь быстро, без необходимости проверять оставшиеся случаи ветвления!
Похоже на то, что использование select здесь не совсем корректно в этом сценарии.
Предполагается, что регистры выбора ветвей должны прослушивать отправляющие и получающие значения канала напрямую или, по желанию, по умолчанию, чтобы при необходимости избежать блокировки.
поэтому исправление, как уже отмечали некоторые люди, помещает долгосрочное задание или процесс в отдельную процедуру и позволяет отправить результат в канал,
а затем в основной процедуре (или любой другой подпрограмме, которой требуется это значение вне канала), используйте регистры выбора ветки, чтобы либо прослушивать значение на этом конкретном канале, либо на канале, предоставленном time.After(time.Second ) позвоните.
По сути, эта строка: case ch & lt; - check (u) является верным в смысле отправки значения в канал, но это не только для его предполагаемого использования (то есть блокирует этот случай ветвления), потому что case channel & lt; - is не блокируется там вообще (проверка времени (u) тратится на все, что происходит до того, как канал включается), поскольку в отдельной программе, известной как главная: return & lt; -ch, он уже готов прочитать это значение всякий раз, когда это проталкивается. Вот почему оператор вызова time.After() во второй ветки case никогда не получит даже возможность быть оцененным, в первом случае!
см. этот пример для простого решения, т.е. правильное использование выбора в сочетании с отдельными программами: https://gobyexample.com/timeouts