我注意到,如果我尝试在 for
循环内使用 goroutines append 到 slice ,在某些情况下我会丢失/空白数据:
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()
有时,当我从 destSlice
打印所有 AttributeName
时,一些元素是空字符串 (""
),而其他时候,一些sourceSlice
中的元素不存在于 destSlice
中。
我的代码是否存在数据竞争,这是否意味着 append
对于多个 goroutine 的并发使用不是线程安全的?
最佳答案
在 Go 中,没有任何值对于并发读/写是安全的, slice (slice headers)也不异常(exception)。
是的,您的代码存在数据竞争。使用 -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
值:
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()
您也可以通过其他方式解决它,例如您可以使用一个 channel ,您可以在该 channel 上发送要追加的值,并让指定的 goroutine 从该 channel 接收并进行追加。
另请注意,虽然 slice header 不安全,但 slice 元素充当不同的变量,并且可以在不同步的情况下并发写入不同的 slice 元素(因为它们是不同的变量)。参见 Can I concurrently write different slice elements
关于go - 追加不是线程安全的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44152988/