一个奇怪的 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 对切片的竞争检测问题

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

查看所有标签

猜你喜欢:

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

Beginning Apache Struts

Beginning Apache Struts

Arnold Doray / Apress / 2006-02-20 / USD 44.99

Beginning Apache Struts will provide you a working knowledge of Apache Struts 1.2. This book is ideal for you Java programmers who have some JSP familiarity, but little or no prior experience with Ser......一起来看看 《Beginning Apache Struts》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

在线进制转换器
在线进制转换器

各进制数互转换器

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码