好坑啊,调用了同事写的基础代码,bug藏得还挺深!!

起因

事情的起因是我调用了同事的一个函数,这个函数返回了一个mapstringstring结构体的变量optionMap(请忽略为什么要返回map结构体,后面有机会再讲),这个函数主要是查DB取获取当前系统的space_id和pkey,返回的内容基本上如下

c 复制代码
 // 返回
 optionMap = map[string]string{
     "space_id":"xxx",
     "pkey": "xxxx",
 }

然后我修改了这个变量,添加了

css 复制代码
optionMap["is_base"] = 1

然后我就return出我当前的函数了,然后在同一个请求内,但是当我再一次请求同事的函数时,返回给我的却是

c 复制代码
 optionMap = map[string]string{
     "space_id":"xxx",
     "pkey": "xxxx",
     "is_base": 1,
 }

what! 怎么后面再次请求同事的的函数总是会多一个参数呢!!!!

经过

看了同事写的函数我才发现,原来他在内部使用了gin上下文去做了一个缓存,大概的代码意思减少重复space基础信息的查询,存入上下文中做缓存,提高代码效率,这里我写了一个示例大家可以看下

go 复制代码
// 同事的代码
func BadReturnMap(ctx *gin.Context, key string) map[string]interface{} {
	m := make(map[string]interface{})
	// 查询缓存
	value, ok := ctx.Get(key)
	if ok {
		bm, ok := value.(map[string]interface{})
		if ok {
			return bm
		}
	}
	// io查询后存入变量
	m["a"] = 1
	// 保存缓存
	fmt.Println("set cache: ")
	fmt.Println(m) // map[a:1]
	ctx.Set(key, m)
	return m
}

// 我的使用
func TestBadReturnMap() {
	fmt.Println("bad return map start")
	ctx := &gin.Context{}
	key := "cached:map_key"
	mapOpt := BadReturnMap(ctx, key)
	fmt.Printf("%p\n", mapOpt) // 0xc0003a6750 指向地址
	mapOpt["b"] = 1
	value, ok := ctx.Get(key)
	fmt.Printf("%p\n", mapOpt) // 0xc0003a6750 指向地址
	if ok {
		fmt.Println("get cache: ")
		fmt.Println(value.(map[string]interface{})) // map[a:1 def:1]
	} else {
		fmt.Println("unknown")
	}
	fmt.Println("bad return map end")
}

打印结果是

arduino 复制代码
bad return map start
set cache: 
map[a:1]
0xc0003a6750
0xc0003a6750
get cache: 
map[a:1 b:1]
bad return map end

解释

在Go语言中,map是引用类型,当将一个map赋值给另一个变量时,实际上是将它们指向同一个底层的map对象。因此,当你修改其中一个变量的map时,另一个变量也会受到影响。

当你将函数内m变量赋值给外部函数内的变量时,它们实际上指向同一个map对象。所以当你在外部函数内修改mapOpt的值时,原始的缓存也会被修改。

如图所示

如何修改

当然修改方式有很多种,我这里列举了一种就是序列化存储到缓存然后反序列化取,如果你有更好的方式可下方留言

go 复制代码
func ReturnMap(ctx *gin.Context, key string) map[string]interface{} {
	m := make(map[string]interface{})
	value, ok := ctx.Get(key)
	if ok {
		bytes := value.([]byte)
		err := json.Unmarshal(bytes, &m)
		if err != nil {
			panic(err)
		}
		return m
	}
	// io查询后存入变量
	m["a"] = 1
	jsonBytes, err := json.Marshal(m)
	if err != nil {
		panic(err)
	}
	fmt.Println("set cache: ")
	fmt.Println(m)
	ctx.Set(key, jsonBytes)

	return m
}

func TestReturnMap() {
	fmt.Println("return map start")
	ctx := &gin.Context{}
	key := "cached:map_key"
	mapOpt := ReturnMap(ctx, key)
	fmt.Printf("%p\n", mapOpt)

	mapOpt["b"] = 1
	fmt.Printf("%p\n", mapOpt)

	value, ok := ctx.Get(key)
	if ok {
		m := make(map[string]interface{})
		bytes := value.([]byte)
		err := json.Unmarshal(bytes, &m)
		if err != nil {
			panic(err)
		}
		fmt.Println("get cache: ")
		fmt.Println(m)
	} else {
		fmt.Println("unknown")
	}
	fmt.Println("return map end")
}

打印的结果为:

arduino 复制代码
return map start
set cache: 
map[a:1]
0xc0003a6870
0xc0003a6870
get cache: 
map[a:1]
return map end

知识点

相关推荐
小小张说故事20 分钟前
Python 的 list 为什么能装下任何类型?拆开 PyObject* 数组,以及它让你多付了多少内存
后端·python
小蒜学长1 小时前
基于SpringBoot 框架的自媒体新闻系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·mysql数据库·自媒体新闻管理系统
IT_陈寒1 小时前
JavaScript实战技巧总结:深挖数组遍历的性能陷阱与实战解法
前端·人工智能·后端
我不会起名字3221 小时前
Redis 从 0 到 1(十三):分布式锁被误删、锁不住?Go 无 Lua 实现 3 级防御
redis·go·分布式锁·watch·setnx
学长毕设11 小时前
基于springboot的食品仓库管理系统
java·spring boot·后端
滴滴答答哒11 小时前
ASP.NET Core + FaceRecognitionDotNet 人脸识别实战:DI 单例、特征向量持久化与 1:N 比对
后端·asp.net
captain37614 小时前
MyBatis操作数据库(入门2)
java·数据库·spring boot·后端·spring·mybatis
IT_陈寒15 小时前
Java序列化踩坑记:对象循环引用让我加班两小时
前端·人工智能·后端
泡海椒15 小时前
05-雷达图多维画像-信用评级多维画像
后端
王中阳Go15 小时前
5.27 分钟干完后端和全部测试:一场三小时笔试的五个失分点,四个不在代码上
大数据·数据库·人工智能·后端·大语言模型