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
からすべてのdestSlice
sを出力すると、一部の要素が空の文字列(""
)、およびその他の場合には、sourceSlice
の一部の要素がdestSlice
に存在しません。
私のコードにはデータ競合がありますか?これは、append
が複数のゴルーチンによる同時使用に対してスレッドセーフではないことを意味しますか?
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を受信して追加を実行できます。
この問題のより最近の解決策を示すために、Goが同期用の新しいマップをリリースしたようです: