Тайм-аут для WaitGroup.Wait()
Что такое идиоматический способ назначить тайм-аут WaitGroup.Wait()?
Причина, по которой я хочу это сделать, заключается в том, чтобы защитить мой "планировщик" от потенциального ожидания "рабочего" бегства навсегда. Это приводит к некоторым философским вопросам (например, как система может быть надежно продолжена, если у нее есть странные рабочие?), Но я думаю, что это не подходит для этого вопроса.
У меня есть ответ, который я предоставлю. Теперь, когда я записал это, это выглядит не так плохо, но все еще кажется более запутанным, чем следовало бы. Я хотел бы знать, есть ли что-то доступное, которое проще, более идиоматично или даже альтернативный подход, который не использует WaitGroups.
Та.
Ответы
Ответ 1
В основном ваше решение, которое вы разместили ниже, так же хорошо, как и может. Несколько советов по его улучшению:
- В качестве альтернативы вы можете закрыть канал для завершения сигнала вместо отправки на него значения, операция приема на закрытом канале всегда может действовать немедленно.
- И лучше использовать инструкцию
defer
для завершения сигнала, она выполняется, даже если функция завершается внезапно.
- Также, если есть только одно "задание" для ожидания, вы можете полностью опустить
WaitGroup
и просто отправить значение или закрыть канал после завершения задания (тот же канал, который вы используете в своем заявлении select
),.
- Указание длительности в 1 секунду так же просто, как:
timeout := time.Second
. Например, укажите 2 секунды: timeout := 2 * time.Second
. Вам не требуется преобразование, time.Second
уже имеет тип time.Duration
, умножая его на нетипизированную константу, подобную 2
, также будет введите значение типа time.Duration
.
Я бы также создал функцию helper/utility, которая обертывала эту функциональность. Обратите внимание, что WaitGroup
должен быть передан как указатель, иначе копия не получит "уведомление" о вызовах WaitGroup.Done()
. Что-то вроде:
// waitTimeout waits for the waitgroup for the specified max timeout.
// Returns true if waiting timed out.
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
c := make(chan struct{})
go func() {
defer close(c)
wg.Wait()
}()
select {
case <-c:
return false // completed normally
case <-time.After(timeout):
return true // timed out
}
}
Используя его:
if waitTimeout(&wg, time.Second) {
fmt.Println("Timed out waiting for wait group")
} else {
fmt.Println("Wait group finished")
}
Попробуйте на Go Playground.
Ответ 2
Я сделал это так: http://play.golang.org/p/eWv0fRlLEC
go func() {
wg.Wait()
c <- struct{}{}
}()
timeout := time.Duration(1) * time.Second
fmt.Printf("Wait for waitgroup (up to %s)\n", timeout)
select {
case <-c:
fmt.Printf("Wait group finished\n")
case <-time.After(timeout):
fmt.Printf("Timed out waiting for wait group\n")
}
fmt.Printf("Free at last\n")
Это прекрасно работает, но это лучший способ сделать это?
Ответ 3
Я написал библиотеку, которая инкапсулирует логику concurrency https://github.com/shomali11/parallelizer, которую вы также можете передать таймауту.
Вот пример без таймаута:
func main() {
group := parallelizer.DefaultGroup()
group.Add(func() {
for char := 'a'; char < 'a'+3; char++ {
fmt.Printf("%c ", char)
}
})
group.Add(func() {
for number := 1; number < 4; number++ {
fmt.Printf("%d ", number)
}
})
err := group.Run()
fmt.Println()
fmt.Println("Done")
fmt.Printf("Error: %v", err)
}
Вывод:
a 1 b 2 c 3
Done
Error: <nil>
Вот пример с таймаутом:
func main() {
options := ¶llelizer.Options{Timeout: time.Second}
group := parallelizer.NewGroup(options)
group.Add(func() {
time.Sleep(time.Minute)
for char := 'a'; char < 'a'+3; char++ {
fmt.Printf("%c ", char)
}
})
group.Add(func() {
time.Sleep(time.Minute)
for number := 1; number < 4; number++ {
fmt.Printf("%d ", number)
}
})
err := group.Run()
fmt.Println()
fmt.Println("Done")
fmt.Printf("Error: %v", err)
}
Вывод:
Done
Error: timeout
Ответ 4
Это не реальный ответ на этот вопрос, но я был (гораздо более простым) решением моей маленькой проблемы, когда у меня был этот вопрос.
Мои "рабочие" выполняли запросы http.Get(), поэтому я просто задал время ожидания на http-клиенте.
urls := []string{"http://1.jpg", "http://2.jpg"}
wg := &sync.WaitGroup{}
for _, url := range urls {
wg.Add(1)
go func(url string) {
client := http.Client{
Timeout: time.Duration(3 * time.Second), // only want very fast responses
}
resp, err := client.Get(url)
//... check for errors
//... do something with the image when there are no errors
//...
wg.Done()
}(url)
}
wg.Wait()
Ответ 5
Это плохая идея. Не отказывайтесь от подпрограмм, это может привести к гонкам, утечке ресурсов и неожиданным условиям, что в конечном итоге повлияет на стабильность вашего приложения.
Вместо этого постоянно используйте тайм-ауты во всем коде, чтобы убедиться, что никакие процедуры не заблокированы навсегда или слишком долго запускаются.
Идиоматический способ достижения этого - context.WithTimeout()
:
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
// Now perform any I/O using the given ctx:
go func() {
err = example.Connect(ctx)
if err != nil { /* handle err and exit goroutine */ }
. . .
}()
Теперь вы можете безопасно использовать WaitGroup.Wait()
, зная, что он всегда закончится своевременно.