我注意到,如果我在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()
有时,当我打印destSlice
中的所有AttributeName
时,有些元素是空字符串(""
),而其他时候,sourceSlice
中的一些元素在destSlice
中不存在。
我的代码是否存在数据竞争,这是否意味着append
对于多个goroutine的并发使用来说不是线程安全的?
5条答案
按热度按时间yyhrrdl81#
在Go语言中,没有一个值对于并发读写是安全的,片(片头)也不例外。
是,您的程式码有数据竞争。请使用
-race
选项执行以进行验证。示例:
运行它与
输出为:
解决方法很简单,用一个
sync.Mutex
来保护写destSlice
的值:你也可以用其他方法来解决这个问题,比如,你可以使用一个通道来发送要追加的值,然后指定一个goroutine从这个通道接收并执行追加。
另请注意,虽然切片头不安全,但切片元素充当不同的变量,并且可以同时写入不同的切片元素而无需同步(因为它们是不同的变量)。请参见我可以同时写入不同的切片元素吗
6za6bjd02#
这是一个很老的问题,但是还有一个小的改进,可以帮助摆脱互斥锁。你可以使用索引来添加到数组中。每个go例程都将使用它自己的索引。在这种情况下,同步是不必要的。
0md85ypi3#
为了给这个问题给予一个更新的解决方案,看起来Go发布了一个新的用于同步的Map:
https://godoc.org/golang.org/x/sync/syncmap
mm9b1k5b4#
问题已经得到了回答,但是我最喜欢的解决这个问题的方法是使用errgroup。文档中的一个例子是这个确切的问题加上一个很好的附加错误处理。
下面是文档中示例的内容:
希望这对那些不知道errgroup包的人有帮助。
wwwo4jvm5#
Go新手请注意--只有@lockwobr的答案会将range语句输出作为新的局部变量进行必要的重新赋值,其作用域将被限制在当前循环迭代中
如果不创建新的变量,所有的迭代(闭包goroutine)都将引用同一个变量,这可能导致值在goroutine之间被跳过或重复
有关https://go.dev/doc/faq#closures_and_goroutines更多信息,请访问www.example.com