Flo*_*ish 9 concurrency append go slice goroutine
我注意到如果我尝试在for循环中使用goroutine附加到切片,则会出现我会丢失/空白数据的情况:
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()
Run Code Online (Sandbox Code Playgroud)
有时,当我打印所有AttributeNames时destSlice,一些元素是空字符串(""),有时,某些元素sourceSlice不存在destSlice.
我的代码是否有数据竞争,这是否意味着append多个goroutine并发使用它不是线程安全的?
icz*_*cza 21
在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()
Run Code Online (Sandbox Code Playgroud)
运行它
go run -race play.go
Run Code Online (Sandbox Code Playgroud)
输出是:
==================
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
Run Code Online (Sandbox Code Playgroud)
解决方法很简单,用一个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()
Run Code Online (Sandbox Code Playgroud)
您也可以通过其他方式解决它,例如,您可以使用您要发送附加值的通道,并从该通道接收指定的goroutine并执行追加.
这是一个很老的问题,但还有一个小的改进可以帮助摆脱互斥锁。您可以使用索引添加到数组。每个 goroutine 都会使用它自己的索引。在这种情况下,不需要同步。
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()
Run Code Online (Sandbox Code Playgroud)
问题已得到解答,但我最喜欢的解决此问题的方法是使用errgroup。文档中的示例之一就是这个确切的问题以及错误处理的一个很好的补充。
以下是文档中示例的主要内容:
g, ctx := errgroup.WithContext(ctx)
searches := []Search{Web, Image, Video}
results := make([]Result, len(searches))
for i, search := range searches {
i, search := i, search // https://golang.org/doc/faq#closures_and_goroutines
g.Go(func() error {
result, err := search(ctx, query)
if err == nil {
results[i] = result
}
return err
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
Run Code Online (Sandbox Code Playgroud)
希望这对那些不了解 errgroup 包的人有所帮助。