2013-10-06 9 views
60

この例の使用例はsync.WaitGroupですか?期待される結果が得られますが、wg.Add(4)wg.Done()の位置についてはわかりません。 wg.Add()で4つのゴルーチンを一度に追加するのは意味がありますか?sync.WaitGroupの例は正しいですか?

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 
+0

()? –

+4

私はこれが古いことを認識していますが、将来の人々のために、関数の開始時に最初の 'defer wg.Done()'呼び出しをお勧めします。 –

答えて

85

はい、この例では正しいです。競合状態を防止するためにgoステートメントの前にwg.Add()が発生することが重要です。以下は、正しい次のようになります。

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()はパラメータに依存します。したがって、Done()の前に、Add()の保証付きになる必要があります。

Goでは、そのような保証はmemory modelによって与えられます。

メモリモデルでは、1つのゴルーチン内のすべてのステートメントが、書き込まれたのと同じ順序で実行されているように表示されます。彼らは実際にはその順序ではない可能性がありますが、その結果はまるでそうであるようになります。また、goroutine doesn't run until after the go statement that calls itが保証されています。 goステートメントの前にAdd()が発生し、Done()の前にgoステートメントが発生するため、Done()の前にAdd()が発生することがわかります。

goステートメントがAdd()の前にある場合は、プログラムが正しく動作する可能性があります。しかし、それは保証されないため、競合状態になります。

+7

私はこれ以上の質問があります:ゴルーチンが取るルートにかかわらず呼び出されるように 'wg.Done()を遅らせる方が良いのではないでしょうか?ありがとう。 –

+2

すべてのgoルーチンが終了する前に関数が復帰していないことを純粋に確認したい場合は、yesが優先されます。待っているグループの全体のポイントは、すべての作業が完了するまで待ってから、あなたが待っていた結果で何かをすることです。 – Zanven

12

doSomething()関数を呼び出すことをお勧めします。呼び出し回数を調整すると、手動で追加パラメータを個別に調整する必要がなくなり、更新するとエラーが発生する可能性があります一方を更新することを忘れてしまいます(この簡単な例ではありそうもありませんが、私は個人的にコード再利用の方が良いと考えています)。

スティーブン・ワインバーグはhis answer to this questionで指摘するように、あなたがgofuncを産卵する前に waitgroup をインクリメントしなければならないのが、あなたはこのように、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") 
} 

http://play.golang.org/p/WZcprjpHa_それはwg.Doneを行うことができます前に、doSomethingの()がクラッシュした場合はどうすれば

関連する問題