WaitGroup.Wait() にタイムアウトを割り当てる慣用的な方法は何ですか?
私がこれをしたい理由は、私の「スケジューラ」が誤った「ワーカー」を永遠に待つことを防ぐためです。これはいくつかの哲学的な質問につながります(つまり、誤った労働者がいたら、システムはどのようにして確実に継続できますか?)が、この質問の範囲外だと思います。
私が提供する答えがあります。書き留めたので、それほど悪くはないように見えますが、それでも、必要以上に複雑に感じられます。より簡単で、より慣用的なもの、またはWaitGroupsを使用しない別のアプローチで利用できるものがあるかどうか知りたいのですが。
た。
主にあなたが投稿したあなたの解決策 下 はそれが得ることができるのと同じくらい良いです。それを改善するためのいくつかのヒント:
defer
ステートメントを使用して完了を通知することをお勧めします。これは、関数が突然終了した場合でも実行されます。WaitGroup
を完全に省略して、値を送信するか、ジョブが完了したときにチャネルを閉じることができます(select
ステートメントで使用するのと同じチャネル)。timeout := time.Second
のように簡単です。たとえば、2秒の指定はtimeout := 2 * time.Second
です。変換は必要ありません。time.Second
はすでにタイプ time.Duration
であり、2
のような型なし定数を乗算すると、time.Duration
型の値も生成されます。この機能をラップするヘルパー/ユーティリティ関数も作成します。 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 で試してください。
私はそれをこのようにしました: 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")
それはうまくいきますが、それはそれを行うための最良の方法ですか?
これはこの質問に対する実際の回答ではありませんが、この質問があったときの私の小さな問題の(はるかに単純な)解決策でした。
私の「ワーカー」は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()
ほとんどの既存の回答は、ゴルーチンのリークを示唆しています。 WaitGroup.Wait にタイムアウトを割り当てる慣用的な方法は、基盤となる sync/atomic パッケージプリミティブを使用することです。 @iczaの回答からコードを取得し、atomic
パッケージを使用してコードを書き直し、タイムアウトを通知する慣用的な方法としてコンテキストキャンセルを追加しました。
package main
import (
"context"
"fmt"
"sync/atomic"
"time"
)
func main() {
var submitCount int32
// run this instead of wg.Add(1)
atomic.AddInt32(&submitCount, 1)
// run this instead of wg.Done()
// atomic.AddInt32(&submitCount, -1)
timeout := time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
fmt.Printf("Wait for waitgroup (up to %s)\n", timeout)
waitWithCtx(ctx, &submitCount)
fmt.Println("Free at last")
}
// waitWithCtx returns when passed counter drops to zero
// or when context is cancelled
func waitWithCtx(ctx context.Context, counter *int32) {
ticker := time.NewTicker(10 * time.Millisecond)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if atomic.LoadInt32(counter) == 0 {
return
}
}
}
}
これは悪い考えです。 goroutinesを破棄しないでください。破棄すると、競合、リソースリーク、予期しない状態が発生し、最終的にアプリケーションの安定性に影響を与える可能性があります。
代わりに、コード全体でタイムアウトを一貫して使用して、ゴルーチンが永久にブロックされたり、実行に時間がかかりすぎたりしないようにします。
これを実現する慣用的な方法は 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()
を安全に使用できるようになりました。常にタイムリーに終了することがわかっています。
同時実行ロジックをカプセル化するライブラリを作成しました 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