问题描述
我注意到,如果我尝试在 for
循环中使用 goroutines 附加到一个切片,就会出现丢失/空白数据的情况:
I noticed that if I tried appending to a slice using goroutines inside a for
loop, there would be instances where I would get missing/blank data:
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
中.
Sometimes, when I print all AttributeName
s from destSlice
, some elements are empty strings (""
), and other times, some elements from sourceSlice
are not present in destSlice
.
我的代码是否存在数据竞争,这是否意味着 append
对于多个 goroutine 并发使用不是线程安全的?
Does my code have a data race, and does this mean that append
is not thread-safe for concurrent use by multiple goroutines?
推荐答案
在 Go 中,并发读/写没有值是安全的,切片(切片标题) 也不例外.
In Go no value is safe for concurrent read/write, slices (which are slice headers) are no exception.
是的,您的代码存在数据竞争.使用 -race
选项运行以进行验证.
Yes, your code has data races. Run with the -race
option to verify.
示例:
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
值:
Solution is simple, use a sync.Mutex
to protect writing the destSlice
value:
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.
You could also solve it in other ways, e.g. you could use a channel on which you'd send the value to be appended, and have a designated goroutine receiving from this channel and do the append.
这篇关于追加不是线程安全的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!