背景
学过java的同学应该比较清楚,string型字符串是不可变字符串,如果对其进行写操作,会重新申请一片内存空间,然后新建一个string,因此java的string可以说是内存安全的。
而在go中,string作为基础数据类型,想当然的会认为string也是并发安全的,然而实际上并不是如此。
代码
概率型panic,可以多尝试几次
package main
import (
"fmt"
"time"
)
func main() {
commonString := "init"
go func() {
for i := 0; i < 10000; i++ {
s := commonString
fmt.Printf("s is %s", s)
}
}()
for {
commonString = ""
time.Sleep(10 * time.Nanosecond)
commonString = "/test/test/test"
time.Sleep(10 * time.Nanosecond)
}
}
------------------运行结果---------------------
goroutine 6 [running]:
fmt.(*buffer).writeString(...)
/usr/local/go/src/fmt/print.go:108
fmt.(*fmt).padString(0x14000068d08?, {0x0, 0xf})
/usr/local/go/src/fmt/format.go:110 +0x22c
fmt.(*fmt).fmtS(0x14000068d98?, {0x0?, 0x14000068d68?})
/usr/local/go/src/fmt/format.go:359 +0x40
fmt.(*pp).fmtString(0x0?, {0x0?, 0x14000068d98?}, 0xb3a248?)
/usr/local/go/src/fmt/print.go:497 +0xe4
fmt.(*pp).printArg(0x14000090000, {0x100b6fe40?, 0x1400009a150}, 0x73)
/usr/local/go/src/fmt/print.go:741 +0x234
fmt.(*pp).doPrintf(0x14000090000, {0x100b460ac, 0x7}, {0x14000068fb8?, 0x1, 0x1})
/usr/local/go/src/fmt/print.go:1077 +0x2e4
fmt.Fprintf({0x100b81fa8, 0x1400000e018}, {0x100b460ac, 0x7}, {0x14000068fb8, 0x1, 0x1})
/usr/local/go/src/fmt/print.go:224 +0x54
fmt.Printf(...)
/usr/local/go/src/fmt/print.go:233
main.main.func1()
/Users/bytedance/GolandProjects/awesomeProject/main.go:13 +0x7c
created by main.main
/Users/bytedance/GolandProjects/awesomeProject/main.go:10 +0x80
原因分析
go中字符串的本质其实是个结构体,如下
type StringHeader struct {
Data uintptr
Len int
}
而在赋值的过程中,go底层对于需要分别对两个值进行赋值,因此无法保证原子,也没有使用锁进做并发安全的保证。因此,当长度被修改,但是指针仍然为空时就会导致panic。所以在实际开发中,尽量避免对同一个字符串并发的频繁的做读写及赋值操作。