最近在字节面试,面试有一个提问:
golang中的string赋值是线程安全的吗?如果是,怎么验证,如果不是,怎么验证
第一反应,golang的string底层结构:
type stringStruct struct {
str unsafe.Pointer
len int
}
其中 str 是一个不变数组,
所以该变字符串的内容都会重新生成一个底层数组,
但在字符串拼接,比如 1000个goroutine 进行字符串拼接操作,最终的结果并不一定是我们预期的,所以,golang string并不是线程安全的,这是我当时的回答。
现在看来,针对这个面试题,这个回答明显不正确,或许是答非所问,
- 我们所说的线程安全,是指操作,而不是指数据类型
- 假如某个操作在执行时,可以通过一条机器指令完成,那么它一定是线程安全的,因为这一条指令是一个原子操作
- 不可改变的数据与线程安全并没有关系,线程安全关注的是操作的安全性
比如,我们对某个变量的读操作,那么它一定是线程安全的,不管是map、sync.map、string、int,这个读操作都是线程安全的。
假如我们对一个 var int i = 10 做自增操作 i++,那么,这个操作就不是并发安全的,因为i++ 在机器指令上可以分为:读取i的值,对i的值做加1操作,这两个操作之间,很明显,会被中断,在多个线程操作的时候,会产生非预期的结果。
所以,面试题中的提问是指golang 中的string类型做 “赋值” 这个操作的时候,是线程安全的吗?
我们先抛开这个问题,考虑一个通用的场景,结构体类型的赋值是线程安全的吗?
比如一个结构体:
type S struct {
b int
a int
}
var v = S{a:1,b:2}
v = S{a:3,b:4}
上面中对变量中v的赋值操作,可分为对字段a的赋值,然后再对b的赋值,这是两个机器指令,所以,如果在两个并发的线程中操作,并不是线程安全的,那么,当结构体中只有一个字段,是否是线程安全的呢?答案是:线程安全的。
同样,假如你只修改结构体中的一个字段,其也是线程安全的,比如你只修改上面的结构体中的a字段,那么是线程安全的。
所以,同样的,对string的 赋值操作,也不是线程安全的,它与string的底层数组是否是不变数组没有关系。
延伸一下,那哪些类型在golang中赋值是线程安全的呢:
字节型、布尔型、整型、浮点型、字符型
以上这些类型的赋值都可以在一条机器指令完成,这儿特别说明一下,以int64位而言,如果在32位的机器上赋值,那么一条指令并不能完成,所以也是不安全的。
另外,指针类型,函数类型的赋值也是线程安全的。
以下类型的赋值不是线程安全的:
数组、切片、字典、通道、接口、sync.map、字符串、复数型的类型的赋值都不是线程安全的
参考文档:https://www.cnblogs.com/sunsky303/p/17082018.html