web-dev-qa-db-ja.com

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

この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
98
topskip

はい、この例は正しいです。 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()の前に置くと、プログラムが正しく動作する可能性があります。ただし、保証されないため、競合状態になります。

143

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")
}

遊び場として: http://play.golang.org/p/WZcprjpHa_

26
mroth
  • mrothの回答に基づく小さな改善
  • doneにdeferを使用する方が安全です
  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")
}
13
Bnaya