首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Go 语言的 append 不总是线程安全的

首发于:https://studygolang.com/articles/13662

示例问题

我经常看到一些 bug 是由于没有在线程安全下在 slice 上进行 append 而引起的。下面用单元测试来举一个简单的例子。这个测试有两个协程对相同的 slice 进行 append 操作。如果你使用 -race flag 来执行这个单元测试,效果更好。

现在,让我们稍微修改代码,以给这个名为 x 的 slice 在创建是预留一些容量。唯一改动的地方是第 9 行。

如果我们执行这个测试时带上 -race flag ,我们可以注意到一个竞争条件。

解释为什么测试失败

理解为什么这个失败会发生,请看看这个旧例子的 x 的内存布局

x 没有足够的容量进行修改

Go 语言发现没有足够的内存空间来存储 "hello", "world" 和 "goodbye", "bob",于是分配的新的内存给 y 与 z。数据竞争不会在多进程读取内存时发生,x 没有被修改。这里没有冲突,也就没有竞争。

z 与 y 获取新的内存空间

在新的代码里,事情不一样了

x 有更多的容量

在这里,go 注意到有足够的内存存放 “hello”, “world”,另一个协程也发现有足够的空间存放 “goodbye”, “bob”,这个竞争的发生是因为这两个协程都尝试往同一个内存空间写入,谁也不知道谁是赢家。

谁赢了?

这是 Go 语言的一个特性而非 bug ,append 不会强制每一次调用它都申请新的内存。它允许用户在循环内进行 append 操作时不会破坏垃圾回收机制。缺点是你必须清楚知道在多个协程对 slice 的操作。

这个 bug 的认知根源

我相信这个 bug 存在是 Go 的为了保存简单,将许多概念放到 slice 中,在大多数开发人员中看到的思维过程是:

x=append(x, …) 看起来你要获得一个新的 slice。

大多数返回值的函数都不会改变它们的输入。

我们使用 append 通常都是得到一个新的 slice。

错误地认为append是只读的。

认知这个 bug

值得注意的是如果第一个被 append 的变量不是一个本地变量(译者:本地变量,即变量与 append 在同一代码块)。这个 bug 通常发生在:进行 append 操作的变量存在一个结构体中,而这个结构体是通过函数传参进来的。例如,一个结构体可以有默认值,可以被各个请求 append。小心对共享内存的变量进行 append ,或者这个内存空间(变量)并不是当前协程独占的。

解决方法

最简单的解决方法是不使用共享状态的第一个变量来进行 append 。相反,根据你的需要来 make 一个新的 slice ,使用这个新的 slice 作为 append 的第一个变量。下面是失败的测试示例的修正版,这里的替代方法是使用 copy 。

对本地变量进行第一次的 append

via: https://medium.com/@cep21/gos-append-is-not-always-thread-safe-a3034db7975

作者:Jack Lindamood

译者:lightfish-zhang

校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20190129B0B6E800?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券