sync.WaitGroupの例は正しいですか?


108

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

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

1
dosomething()がwg.Done()を実行する前にクラッシュするとどうなりますか?
モストウスキー崩壊2016

19
これは古いことだと思いますが、将来の人々のためdefer wg.Done()に、関数の最初に最初の呼び出しをすることをお勧めします。
ブライアン

回答:


154

はい、この例は正しいです。競合状態を防ぐ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()がaで-1あり、それぞれAdd()パラメーターに依存します。したがって、カウンタが決して下回らないようにし、パニックを回避するには、がの前に来るAdd()ことが保証されている必要がありますDone()

Goでは、このような保証はメモリモデルによって提供されます

メモリモデルでは、1つのgoroutine内のすべてのステートメントは、記述されたのと同じ順序で実行されているように見えます。実際にはそれらの順序にならない可能性もありますが、結果はあたかもそうであるかのようになります。また、それを呼び出すステートメントの後までgoゴルーチンが実行されないことも保証されてます。以来Add()前に発生したgo声明とgo文が前に発生したDone()、私たちは知っているAdd()前に発生しますDone()

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


9
私はこれについて質問があります:defer wg.Done()goroutineが取るルートに関係なく、確実に呼び出されるようにした方がいいのではないでしょうか?ありがとう。
アレッサンドロサンティーニ2015年

2
すべてのgoルーチンが完了する前に関数が戻らないことを確認したい場合は、はい延期が推奨されます。通常、待機グループの全体のポイントは、すべての作業が完了するまで待機してから、待機していた結果で何かを実行することです。
ザンヴェン2015年

1
使用せずdefer、ゴルーチンの1つが呼び出しに失敗した場合wg.Done()は、Wait単に永久にブロックしませんか?これは、コードに見つけにくいバグを簡単に導入する可能性があるように聞こえます...
Dan Esparza 2018

29

関数自体にwg.Add()呼び出しを埋め込むことをお勧めします。これにより、呼び出されるdoSomething()回数を調整する場合、手動で追加パラメーターを調整する必要がなく、更新パラメーターを更新し忘れた場合にエラーが発生する可能性があります。その他(この些細な例ではありそうもありませんが、それでも、私は個人的にはコードを再利用するためのより良い実践であると信じています)。

Stephen Weinberg がこの質問への回答で指摘しているように、gofunc を生成するにウェイトグループをインクリメントする必要がありますが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_


21
  • Mrothの回答に基づく小さな改善
  • 完了に延期を使用する方が安全です
  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")
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.