Go 附加不是线程安全的吗?
我注意到,如果我尝试在Go 附加不是线程安全的吗?,go,concurrency,append,slice,goroutine,Go,Concurrency,Append,Slice,Goroutine,我注意到,如果我尝试在for循环中使用goroutines附加到切片,可能会出现丢失/空白数据的情况: 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
for
循环中使用goroutines附加到切片,可能会出现丢失/空白数据的情况:
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
中不存在
我的代码是否存在数据竞争,这是否意味着对于多个goroutine并发使用,
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
解决方案很简单,使用保护写入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()
您也可以通过其他方式解决此问题,例如,您可以使用一个频道,在该频道上发送要追加的值,并让指定的goroutine从此频道接收并执行追加操作。要提供此问题的最新解决方案,Go似乎发布了一个用于同步目的的新地图:
这是一个很老的问题,但还有另一个小的改进,有助于消除互斥。您可以使用索引添加到数组中。每个go例程都将使用自己的索引。在这种情况下,不需要同步
destSlice := make([]myClass, len(sourceSlice))
var wg sync.WaitGroup
for i, myObject := range sourceSlice {
wg.Add(1)
go func(idx int, closureMyObject myClass) {
defer wg.Done()
var tmpObj myClass
tmpObj.AttributeName = closureMyObject.AttributeName
destSlice[idx] = tmpObj
}(i, myObject)
}
wg.Wait()
谢谢我会调查的!事实上,这是一个方便的解决方案,当你事先知道你的集合的大小!非常感谢分享!