反序列化+引用+全局变量
先看一段代码
下面这段代码的初衷是给 config.Cluster字段先赋值一个默认值,然后根据conf 配置【json 字符串】来覆盖config.Cluster字段,如果conf 配置中没有 cluster这个key,config.Cluster就会保持默认值
var defaultCluster = "default_cluster"
type Config struct {
Cluster *string `json:"cluster"`
}
func (c *Config) parse(conf string) {
_ = json.Unmarshal([]byte(conf), &c)
}
func main() {
c0 := Config{Cluster: &defaultCluster}
conf0 := `{}`
c1 := Config{Cluster: &defaultCluster}
conf1 := `{"cluster":"test_cluster_1"}`
c0.parse(conf0)
fmt.Printf("before unmarshal conf1:\n")
fmt.Printf(" defaultCluster:%s\n", defaultCluster)
fmt.Printf(" c0.Cluster:%s\n", *c0.Cluster)
c1.parse(conf1)
fmt.Printf("after unmarshal conf1:\n")
fmt.Printf(" defaultCluster:%s\n", defaultCluster)
fmt.Printf(" c0.Cluster:%s\n", *c0.Cluster)
}
可以猜一下输出
可以发现第 21 行代码反序列化执行之后,全局变量defaultCluster被修改了,并且c1.Cluster的值也被修改了
before unmarshal conf1:
defaultCluster:default_cluster
c0.Cluster:default_cluster
after unmarshal conf1:
defaultCluster:test_cluster_1
c0.Cluster:test_cluster_1
看反序列化的源码可以知道,如果要写入的字段是引用类型,并且不为空,会一层一层解引用,对最终指向的内存地址赋值,也就是对全局变量default_cluster直接赋值,并且不会改变c1.Cluster->default_cluster
这层引用关系
踩坑
上面这段代码在并发的情况下会导致根据 conf 解析出来的 cluster ,在使用的时候会被其他协程修改掉
原因就在于
- Config 实例对象Cluster 字段引用了全局变量
- 该全局变量会因为不同 Config对象使用不同 conf 配置调用parse函数时,会将该全局变量修改为不同的值
var defaultCluster = "default_cluster"
type Config struct {
Cluster *string `json:"cluster"`
}
func (c *Config) parse(conf string) {
_ = json.Unmarshal([]byte(conf), &c)
}
func main() {
waitGroup := sync.WaitGroup{}
for i := 0; i < 10; i++ {
waitGroup.Add(1)
go func(index int) {
defer waitGroup.Done()
c := Config{Cluster: &defaultCluster}
conf := fmt.Sprintf(`{"cluster":"cluster_%d"}`, index)
c.parse(conf)
time.Sleep(time.Microsecond * 10) // 10us
fmt.Println(*c.Cluster == fmt.Sprintf("cluster_%d", index))
}(i)
}
waitGroup.Wait()
}
输出:
false
false
false
false
false
true
false
false
false
true
修改
有两种解法
- 空结构体接收反序列化数据,判断 Cluster 是否为空,为空再设置默认值
c0 := Config{}
conf0 := `{}`
c0.parse(conf0)
if c0.Cluster == nil {
c0.Cluster = &defaultCluster
}
- defaultCluster从全局变量变为函数内局部变量