关于 golang 碰到的一个问题!

2021-11-30 09:30:23 +08:00
 sunny1688

直接上代码,请看图: https://pic.baixiongz.com/uploads/2021/11/30/bc91319946394.jpeg

搞不明白为什么 append slice 会 panic ,出现空指针,而且不是必现,运行一段时间才会出现,一般在几个小时内,求大佬解释一下是为什么

3476 次点击
所在节点    问与答
29 条回复
jimmzhou
2021-11-30 10:56:11 +08:00
go run -race 跑一下 会发现 WARNING: DATA RACE
loushizan
2021-11-30 14:02:44 +08:00
@mangoDB 👍
不过准确的说,是 slice 指向的数组指针发生了变化
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
Data 发生了变化,slice 本身不会
Marmot
2021-11-30 14:13:55 +08:00
@imherer 这个老哥回答的才是对的,也是上面说的为什么需要加锁的原因,slice 的底层是一个数组,当触发扩容之后,会把内容 copy 到新的内存地址上面去,然后 gc 回收旧的那个,但是有些 gorountie 还在往上面写
icexin
2021-11-30 14:50:41 +08:00
大家回答的点都集中在内存回收上,实际的问题是没有加锁导致的不变式被打破的问题。

实际的 slice 包含 data ,len 和 cap 字段,这些大家也都知道了。slice 结构的不变式是:在任意时刻,data 指向的数据长度都是至少是 len 长度,否则访问 len-1 的数据就会 内存错误。

在题主的代码里面,多个 goroutine 同时对 demo.llist 进行赋值,但因为没有加锁,所以赋值不是原子的,从而会出现一个 goroutine 刚赋值了 data ,还没来得及赋值 data 和 cap 就被其他 goroutine 拿去用了, 破坏了不变式, 从而在扩容的时候就访问了非法内存,从而 panic 。

一段简单代码就可以复现:


package main

import "log"

type T struct {
A, B int
}

func step(t T) T {
if t.B != t.A*2 {
log.Panic(t)
}
x := t.A+1
return T{
A: x,
B: 2*x,
}
}

func main() {
var t = T{
A: 1,
B: 2,
}
for {
go func() {
t = step(t)
}()
}
}
quzard
2021-11-30 17:38:15 +08:00
list 没初始化
ruyiL
2021-11-30 17:41:58 +08:00
这个地方扩容应该是不安全的,但是一般 slice 比较小的时候扩容比较快,所以不容易出问题。
底层的扩容逻辑实际上是开辟一个新数组,然后将 value 拷贝过去,然后将指针指过去,但是当 slice 内存过大之后,这个拷贝的过程是比较漫长的,竞态问题就出现了
labulaka521
2021-11-30 22:38:03 +08:00
额 推荐下 用 https://go.dev/play 来贴 golang 代码
sunny1688
2021-12-01 17:06:15 +08:00
@labulaka521 感谢
voocel
2021-12-03 10:38:13 +08:00
slice make 一下应该就不会有问题了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/818927

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX