一个奇怪的 Golang 对切片的竞争检测问题

栏目: Go · 发布时间: 5年前

内容简介:1、首先,在开始之前,先说一点相关的东西。在 Golang 中,有很多数据结构的操作,都不是线程安全的,比如大家熟知的 map ,比如 container/list 包。线程安全,指的是基于这类数据结构实例化的变量,可以并发操作,也就是多个 goroutine 同时进行操作。另外,也许你也知道,golang 在编译时,是支持并发竞争检测的。go build --race ,很多 gopher 其实并不陌生。这里需要说一点是,--race 并非只支持构建时,也支持单测时,也就是 go test --race。

问题示例

1、首先,在开始之前,先说一点相关的东西。

在 Golang 中,有很多数据结构的操作,都不是线程安全的,比如大家熟知的 map ,比如 container/list 包。线程安全,指的是基于这类数据结构实例化的变量,可以并发操作,也就是多个 goroutine 同时进行操作。

另外,也许你也知道,golang 在编译时,是支持并发竞争检测的。go build --race ,很多 gopher 其实并不陌生。这里需要说一点是,--race 并非只支持构建时,也支持单测时,也就是 go test --race。

好了,结合上面2个点,我们看一个例子(文件 xx_test.go)(代码示例1):

代码很简单,初始化一个切片,起2个协程,并发操作这个切片。

我们做一下单测并做竞争检测:

从结果来看,竞争检测结果是通过的。

2、我们将上面的代码做一点变更,上面代码第 9 行,切片初始化是这样的:

我们做一个改动,给它一个大小

仅此而已,什么都不变,然后我们再看一下完整的代码,并再次做一次竞争检测(代码示例2)。

我们再次做测试,看一下测试结果:

结果:

很直接,golang 直接告诉我们有数据竞争,数据竞争检测不通过。而我们仅仅只改了 slice 的初始化方式而已。

为什么测试会失败

要理解为什么会失败,就需要看我们2个例子中,切片的内存变化。

代码示例1的切片内存布局

竞争检测通过的代码示例1(也就是第一个代码例子)中的 x 初始化方式我们回顾一下:

一个奇怪的 Golang 对切片的竞争检测问题

在这个切片中,名称为 x ,长度为 1,容量也为 1 。

但是需要注意,在代码示例1,2个不同的协程,要向 x 中分别添加元素:"hello", "world" 和 "goodbye", "bob" ,所以,Golang 需要新开辟内存空间,切片 x 的内存变化如图:

一个奇怪的 Golang 对切片的竞争检测问题

这个图有几个关键点:

  1. 原始切片为 x,长度和容量都是 1。

  2. 协程1为切片 x,添加元素,并将结果赋值给新的变量 y。相当于直接开辟了内存空间 y,做元素新增的操作。

  3. 协程2为切片 x,添加元素,并将结果赋值给新的变量 z。相当于直接开辟了内存空间 z,做元素新增的操作。

  4. 当多个线程读取内存 x 时,由于 x 底层一直就没变化,因此,不会发生数据争用。竞争检测是通过的。

代码示例2的切片内存布局

在后来的例子,也就是代码示例2中,代码有所变化,我们回顾一下:

一个奇怪的 Golang 对切片的竞争检测问题

从图中可以看到,切片 x 的内存布局有所变化,长度为 0,但是容量为 6。在代码示例2中,有2个协程,在往 x 中,分别添加2个 元素。问题是,在这个切片 x 中,是有足够的空间,可以放下 6个新元素的。因此,协程1和协程2,都会往切片 x 的内存空间中,添加新元素。

而竞争,就是发生是因为两个goroutine都试图写入相同的内存区域。因此,数据竞争产生了。golang test --race 也就失败了。

竞争对切片 x 写数据的示意图如下:

一个奇怪的 Golang 对切片的竞争检测问题

如图,协程1 和 协程2 ,竞争操作了同一个切片 x。最终也不知道谁赢了。

结论:

在 Golang 的切片操作中,每次调用 append 并不会强制执行新的内存分配。因此,上面的情况,这是 golang 本身的特性,而不是bug。

如何避免上述问题

解决方式1:预先分配好目标变量内存

最简单的解决方法是,做 append 操作时,如果你希望 append 后是一个新的数据,那么,一开始就不要不使用有共享状态的变量,作为要追加的第一个变量。

比如,使用你需要的总容量创建一个新切片,并使用新切片作为要追加的第一个变量。

下面是一个代码示例:

总的来说(以协程1的操作为例):

  1. append 之前,先创建新的变量 y。

  2. 将 x 原有的数据,添加到 y 中。

  3. 执行你需要 append 的新元素。

这个操作其实有点繁琐,谈不上优雅,而且内存效率也有一定程度上的浪费。

解决方式2:加锁

当然,如果你有更好的解决方式,欢迎指正。

欢迎关注“海角之南”公众号获取更新动态

一个奇怪的 Golang 对切片的竞争检测问题

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

用户体验面面观

用户体验面面观

[美] 库涅夫斯基(Mike Kuniavsky) / 汤海 / 清华大学出版社 / 2010-5 / 69.00

这是一本专注于用户研究和用户体验的经典读物,同时也是一本容易上手的实战手册,从实践者的角度,着重讨论和阐述了用户研究的重要性、主要的用户研究方法和工具,同时借助于实例介绍了相关的应用。全书共3部分18章,深度剖析了何为优秀的用户设计,用户体验包括哪些研究方法和工具,如何 得出和分析用户体验调查结果等。一起来看看 《用户体验面面观》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换