このsync.WaitGroup
の使用例は正しいですか?期待どおりの結果が得られますが、wg.Add(4)
とwg.Done()
の位置についてはわかりません。 wg.Add()
で4つのゴルーチンを一度に追加するのは理にかなっていますか?
http://play.golang.org/p/ecvYHiie0P
package main
import (
"fmt"
"sync"
"time"
)
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(4)
go dosomething(200, &wg)
go dosomething(400, &wg)
go dosomething(150, &wg)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
結果(予想どおり):
Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
はい、この例は正しいです。 wg.Add()
は、競合状態を防ぐためにgo
ステートメントの前に発生することが重要です。以下も正しいでしょう:
func main() {
var wg sync.WaitGroup
wg.Add(1)
go dosomething(200, &wg)
wg.Add(1)
go dosomething(400, &wg)
wg.Add(1)
go dosomething(150, &wg)
wg.Add(1)
go dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
ただし、wg.Add
を何度呼び出すかは、それが何回呼び出されるかをすでに知っているので、無意味です。
Waitgroups
カウンターがゼロを下回るとパニックになります。カウンターはゼロから始まり、各Done()
は-1
であり、各Add()
はパラメーターに依存します。したがって、カウンタが絶対に下に落ちないようにしてパニックを回避するには、Add()
をguaranteedにしてからDone()
。
Goでは、このような保証は メモリモデル で与えられます。
メモリモデルには、単一のゴルーチン内のすべてのステートメントが、記述された順序と同じ順序で実行されるように見えると記載されています。実際にその順序にならない可能性がありますが、結果はあたかもそうであるかのようになります。また、 goroutineはそれを呼び出すgo
ステートメントの後まで実行されない が保証されます。 Add()
はgo
ステートメントの前にあり、go
ステートメントはDone()
の前にあるため、Add()
はDone()
の前にあることがわかります。
go
ステートメントをAdd()
の前に置くと、プログラムが正しく動作する可能性があります。ただし、保証されないため、競合状態になります。
wg.Add()
呼び出しをdoSomething()
関数自体に埋め込むことをお勧めします。これにより、呼び出される回数を調整する場合、addパラメーターを個別に手動で調整する必要がないため、片方を更新し、もう片方を更新するのを忘れるとエラーになります(この些細な例ではありそうにありませんが、それでもコードの再利用のより良い実践であると私は信じています)。
Stephen Weinbergが この質問に対する答え で指摘しているように、gofuncを生成するには、waitgrouppriorを増やす必要があります。ただし、次のようにdoSomething()
関数自体の中にgofuncスポーンをラップすることで、これを簡単に実現できます。
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
duration := millisecs * time.Millisecond
time.Sleep(duration)
fmt.Println("Function in background, duration:", duration)
wg.Done()
}()
}
次に、go
の呼び出しなしで呼び出すことができます。例:
func main() {
var wg sync.WaitGroup
dosomething(200, &wg)
dosomething(400, &wg)
dosomething(150, &wg)
dosomething(600, &wg)
wg.Wait()
fmt.Println("Done")
}
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) { wg.Add(1) go func() { defer wg.Done() duration := millisecs * time.Millisecond time.Sleep(duration) fmt.Println("Function in background, duration:", duration) }() } func main() { var wg sync.WaitGroup dosomething(200, &wg) dosomething(400, &wg) dosomething(150, &wg) dosomething(600, &wg) wg.Wait() fmt.Println("Done") }