go语言并发读写string时崩溃
大约 2 分钟...
go语言并发读写string时崩溃
描述
业务中遇到一个case,最后定位的结果是两个go程同时读写一个string类型的变量导致了panic
更新后业务不再panic,但是两个string为什么会发生panic呢?这里需要深入探究一下
分析
在网上搜索了一圈,没有找到合理且可以试验证实的结论,故尝试自己分析一下源码
string源码
提示
版本: go1.20.1
路径: src/runtime/string.go
type stringStruct struct {
str unsafe.Pointer
len int
}
这里可以看到,string的本质是一个 byte数组 + byte数目 的结构
既然是结构体,赋值就有先后,不加锁的前提下,string是一个非并发安全的结构,但是非并发安全不意味着一定会panic。
这里有两个疑问
- 不加锁的情况下,是先赋值 str,还是先赋值 len
- panic是怎么发生的
尝试复现
这里启动两个go程分别读写操作,其中
- go程1 将str的值修改为 "x" ,再改成 "abc",再改成 "x" ,以此往复
- 主go程 将str输出
func main() {
var str string
go func() { // go程1
var i int
for {
if i%2 == 0 {
str = "x"
} else {
str = "abc"
}
i++
}
}()
time.Sleep(time.Second) // 防止输出空格
for i := 0; i < 50; i++ {
fmt.Println(str)
}
}
输出的结果如下
abc
x
abc
x
x
a
abc
x
abc
x
x
abc
x
abc
abc
abc
x
x
x{}
这里一共有4种结果:abc、x、a、x{},其中的abc与x很容易理解,属于正常的赋值。
而 a、x{} 这两个结果,却很意外。我们尝试模拟操作步骤的方法来理解一下这两个结果的来源
模拟结果
string结构包括 str 与 len 两部分,那么下面使用 str: abc, len: 3 这样的形式来表示string的状态
结合前面对string结构的分析,string的赋值可以分为两步
- 赋值str
- 赋值len
当然也可以反过来,这里需要结合代码的输出证实一下
对于 结果a,可能的过程如下:
步骤 | go程1 | 主go程(go程2) | string状态 |
---|---|---|---|
初始化 | - | - | str: abc, len: 3 |
第一步 | len = 1 | - | str: abc, len: 1 |
第二步 | - | [str: abc, len: 1] 输出a | str: abc, len: 1 |
第三步 | str = x | - | str: x, len: 1 |
对于 结果x{},可能的过程如下:
步骤 | go程1 | 主go程(go程2) | string状态 |
---|---|---|---|
初始化 | - | - | str: abc, len: 3 |
第一步 | str = x | - | str: x, len: 3 |
第二步 | - | [str: x, len: 3] 输出x{} | str: x, len: 3 |
第三步 | len = 1 | - | str: x, len: 1 |
结论
参考
Powered by Waline v2.15.6