我注意到,如果我尝试在for循环内使用goroutines将其追加到切片,那么在某些情况下我会丢失/空白数据:
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从中打印all 时destSlice,某些元素是空字符串(""),而在另一些sourceSlice情况下,in中则不存在某些元素destSlice。
AttributeName
destSlice
""
sourceSlice
我的代码是否存在数据争用,是否意味着append对于多个goroutine并发使用来说不是线程安全的?
append
在Go中,没有值可以安全地进行并行读/写操作,切片(切片头)也不例外。
是的,您的代码存在数据竞争。运行带有-race选项的验证。
-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值:
sync.Mutex
var ( mu = &sync.Mutex{} 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 mu.Lock() destSlice = append(destSlice, tmpObj) mu.Unlock() }(myObject) } wg.Wait()
您还可以通过其他方式解决它,例如,可以使用一个通道,在该通道上发送要附加的值,并从该通道接收指定的goroutine并执行附加操作。