跳至主要內容

go语言并发读写string时崩溃

大约 2 分钟...go后端

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。

这里有两个疑问

  1. 不加锁的情况下,是先赋值 str,还是先赋值 len
  2. 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的赋值可以分为两步

  1. 赋值str
  2. 赋值len

当然也可以反过来,这里需要结合代码的输出证实一下

对于 结果a,可能的过程如下:

步骤go程1主go程(go程2)string状态
初始化--str: abc, len: 3
第一步len = 1-str: abc, len: 1
第二步-[str: abc, len: 1] 输出astr: 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