前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言中常见100问题-#69 Creating data races with append

Go语言中常见100问题-#69 Creating data races with append

作者头像
数据小冰
发布2022-08-15 15:26:15
4660
发布2022-08-15 15:26:15
举报
文章被收录于专栏:数据小冰
append操作可能会产生数据竞争

在前一节内容Go语言中常见100问题-#68 Forgetting about possible side-effects with ...讲述了什么是数据竞争以及竞争产生的问题。本节内容将讨论切片话题并分析通过append向切片中添加元素是否存在竞争问题。提前透露一下,是否存在竞争视具体情况而定。

下面的例子中,先初始一个切片s,然后创建两个goroutine,在每个goroutine中通过append创建一个新的切片,并向里面添加一个元素。代码如下,完整可以运行的代码见(https://github.com/ThomasMing0915/100-go-mistakes-code/tree/main/69)

代码语言:javascript
复制
s := make([]int, 1)

go func() {
        s1 := append(s, 1)
        fmt.Println(s1)
}()

go func() {
        s2 := append(s, 1)
        fmt.Println(s2)
}()

上述程序是否存在数据竞争?答案是不存在。在分析原因之前,我们先来看看切片的基础知识,切片背后是有一个底层数组支撑的,它有两个属性:长度和容量。长度是切片中可用元素的数量,容量是底层数组的大小。当用append操作切片时,行为取决于切片是否已满(长度和容量相等)。如果已满,它会创建一个新的底层数组来添加元素,否则会添加元素到当前的底层数组中。上面的例子中,通过make创建了一个大小和容量都为1的切片s. 因此s已满,在每个goroutine中使用append向里面添加元素的时候,都会创建一个新的底层数组,不会改动s的底层数组,所以不存在数据竞争。

下面对上面的代码稍做改动,变化的地方是初始化s的时候,将创建一个长度为0但容量为1的切片。代码如下, 这种情况存在数据竞争吗?答案是存在数据竞争。这里切片s的长度和容量不等,即切片s还没有满,两个goroutine都尝试向切片s的底层数组的相同位置添加元素,导致了数据竞争产生。

代码语言:javascript
复制
s := make([]int, 0, 1)

// Same

运行上述程序,加入-race参数检查数据竞争,在控制台输出结果如下:

代码语言:javascript
复制
go run -race  example2.go                                                                                       
[1]
==================
WARNING: DATA RACE
Write at 0x00c00001c0f8 by goroutine 8:

对于上面这种情况,如果希望两个goroutine都能够正常操作s, 并向里面添加元素,即怎么消除这里的数据竞争问题呢?一种解决方法是通过创建副本的方式,代码如下。

代码语言:javascript
复制
s := make([]int, 0, 1)

go func() {
        sCopy := make([]int, len(s), cap(s))
        copy(sCopy, s)

        s1 := append(sCopy, 1)
        fmt.Println(s1)
}()

go func() {
        sCopy := make([]int, len(s), cap(s))
        copy(sCopy, s)

        s2 := append(sCopy, 1)
        fmt.Println(s2)
}()

两个goroutine都是先创建s的副本,然后在副本上使用append进行元素追加操作,而不是在s上进行追加。这种将两个goroutine工作在隔离的数据上,防止产生数据竞争。

「NOTE:多个goroutine并发访问切片或map时,产生的数据竞争情况如何?」

  • 如果它们访问切片中相同的位置,并且至少有一个goroutine在更新位置中的值,这种情况存在数据竞争,因为有多个goroutine试图对内存中的同个位置进行读写操作
  • 如果它们访问切片中不同的位置,无论执行的是什么操作,不存在数据竞争,因为多个goroutine操作的是内存中的不同位置
  • 如果它们访问相同的map,无论是否操作的是相同的键,只要有其中一个goroutine在执行更新操作,就存在数据竞争。你也许想知道它为什么与切片不同?因为map是一个桶数组,每个桶是一个指向键值对数组的指针。哈希算法用于确定存储桶的数组索引,由于该算法在map初始化期间带有随机性,因此存在一次执行可能会导致相同的数组索引,而另一次执行可能不会的情况。所以无论实际是否真的存在数据竞争,竞争检测器都视为存在竞争,发出警告信息。

在并发上下文环境中使用切片时,必须记住,在切片上使用append操作并不总是没有数据竞争的。具体行为依赖于切片是否已满,如果切片已满,则追加操作是无竞争的,否则如果切片没有满,多个goroutine可能会竞争更新相同的数组索引位置的数据,从而导致数据竞争。一般来说,我们不应该根据切片是否已满进行不同的编码实现,应该考虑到在并发应用程序中对共享切片使用append可能会导致数据竞争,因此应该避免它的产生。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据小冰 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • append操作可能会产生数据竞争
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档