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

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

内容简介: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 对切片的竞争检测问题

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

查看所有标签

猜你喜欢:

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

黑客攻防技术宝典(第2版)

黑客攻防技术宝典(第2版)

[英] Dafydd Stuttard、[英] Marcus Pinto / 石华耀、傅志红 / 人民邮电出版社 / 2012-6-26 / 99.00元

内容简介: Web应用无处不在,安全隐患如影随形。承载着丰富功能与用途的Web应用程序中布满了各种漏洞,攻击者能够利用这些漏洞盗取用户资料,实施诈骗,破坏其他系统等。近年来,一些公司的网络系统频频遭受攻击,导致用户信息泄露,造成不良影响。因此,如何确保Web应用程序的安全,已成为摆在人们眼前亟待解决的问题。 本书是Web安全领域专家的经验结晶,系统阐述了如何针对Web应用程序展开攻击与......一起来看看 《黑客攻防技术宝典(第2版)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码