web-dev-qa-db-ja.com

スレッドセーフではない追加?

forループ内のゴルーチンを使用してスライスに追加しようとすると、データが欠落/空白になる場合があることに気づきました。

destSlice := make([]myClass, 0)

var wg sync.WaitGroup
for _, myObject := range sourceSlice {
    wg.Add(1)
    go func(closureMyObject myClass) {
        defer wg.Done()
        var tmpObj myClass
        tmpObj.AttributeName = closureMyObject.AttributeName
        destSlice = append(destSlice, tmpObj)
    }(myObject)
}
wg.Wait()

時々、AttributeNameからすべてのdestSlicesを出力すると、一部の要素が空の文字列("")、およびその他の場合には、sourceSliceの一部の要素がdestSliceに存在しません。

私のコードにはデータ競合がありますか?これは、appendが複数のゴルーチンによる同時使用に対してスレッドセーフではないことを意味しますか?

9

Goでは、同時読み取り/書き込みに対して値は安全ではありません。スライス( スライスヘッダー )も例外ではありません。

はい、コードにはデータ競合があります。確認するには、-raceオプションを指定して実行します。

例:

type myClass struct {
    AttributeName string
}
sourceSlice := make([]myClass, 100)

destSlice := make([]myClass, 0)

var wg sync.WaitGroup
for _, myObject := range sourceSlice {
    wg.Add(1)
    go func(closureMyObject myClass) {
        defer wg.Done()
        var tmpObj myClass
        tmpObj.AttributeName = closureMyObject.AttributeName
        destSlice = append(destSlice, tmpObj)
    }(myObject)
}
wg.Wait()

で実行する

go run -race play.go

出力は次のとおりです。

==================
WARNING: DATA RACE
Read at 0x00c420074000 by goroutine 6:
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0x69

Previous write at 0x00c420074000 by goroutine 5:
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0x106

Goroutine 6 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb

Goroutine 5 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb
==================
==================
WARNING: DATA RACE
Read at 0x00c42007e000 by goroutine 6:
  runtime.growslice()
      /usr/local/go/src/runtime/slice.go:82 +0x0
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0x1a7

Previous write at 0x00c42007e000 by goroutine 5:
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0xc4

Goroutine 6 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb

Goroutine 5 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb
==================
==================
WARNING: DATA RACE
Write at 0x00c420098120 by goroutine 80:
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0xc4

Previous write at 0x00c420098120 by goroutine 70:
  main.main.func1()
      /home/icza/gows/src/play/play.go:20 +0xc4

Goroutine 80 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb

Goroutine 70 (running) created at:
  main.main()
      /home/icza/gows/src/play/play.go:21 +0x1cb
==================
Found 3 data race(s)
exit status 66

解決策は単純です。_ sync.Mutex を使用して、destSlice値の書き込みを保護します。

destSlice := make([]myClass, 0)
mux := &sync.Mutex{}

var wg sync.WaitGroup
for _, myObject := range sourceSlice {
    wg.Add(1)
    go func(closureMyObject myClass) {
        defer wg.Done()
        var tmpObj myClass
        tmpObj.AttributeName = closureMyObject.AttributeName
        mux.Lock()
        destSlice = append(destSlice, tmpObj)
        mux.Unlock()
    }(myObject)
}
wg.Wait()

また、他の方法で解決することもできます。追加する値を送信するチャネルを使用し、このチャネルから指定されたgoroutineを受信して​​追加を実行できます。

20
icza

この問題のより最近の解決策を示すために、Goが同期用の新しいマップをリリースしたようです:

https://godoc.org/golang.org/x/sync/syncmap

2
cody.tv.weber