这篇文章主要为大家展示了“如何给Golang map做GC”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何给Golang map做GC”这篇文章吧。
创新互联建站长期为上千家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为婺源企业提供专业的网站设计制作、成都网站制作,婺源网站改版等技术服务。拥有十载丰富建站经验和众多成功案例,为您定制开发。
在 Golang中的 map 结构,在删除键值对的时候,并不会真正的删除,而是标记。那么随着键值对越来越多,会不会造成大量内存浪费?
首先答案是会的,很有可能导致 OOM,而且针对这个还有一个讨论:github.com/golang/go/issues/20135。大致的意思就是在很大的 map 中,delete 操作没有真正释放内存而可能导致内存 OOM。
所以一般的做法:就是 重建map。而 go-zero 中内置了 safemap 的容器组件。safemap 在一定程度上可以避免这种情况发生。
那首先我们看看 go 原生提供的 map 是怎么删除的?
原生map删除
1 package main
2
3 func main() {
4 m := make(map[int]string, 9)
5 m[1] = "hello"
6 m[2] = "world"
7 m[3] = "go"
8
9 v, ok := m[1]
10 _, _ = fn(v, ok)
11
12 delete(m, 1)
13 }
14
15 func fn(v string, ok bool) (string, bool) {
16 return v, ok
17 }测试代码如上,我们可以通过 go tool compile -S -N -l testmap.go | grep "CALL" :
0x0071 00113 (test/testmap.go:4) CALL runtime.makemap(SB) 0x0099 00153 (test/testmap.go:5) CALL runtime.mapassign_fast64(SB) 0x00ea 00234 (test/testmap.go:6) CALL runtime.mapassign_fast64(SB) 0x013b 00315 (test/testmap.go:7) CALL runtime.mapassign_fast64(SB) 0x0194 00404 (test/testmap.go:9) CALL runtime.mapaccess2_fast64(SB) 0x01f1 00497 (test/testmap.go:10) CALL "".fn(SB) 0x0214 00532 (test/testmap.go:12) CALL runtime.mapdelete_fast64(SB) 0x0230 00560 (test/testmap.go:7) CALL runtime.gcWriteBarrier(SB) 0x0241 00577 (test/testmap.go:6) CALL runtime.gcWriteBarrier(SB) 0x0252 00594 (test/testmap.go:5) CALL runtime.gcWriteBarrier(SB) 0x025c 00604 (test/testmap.go:3) CALL runtime.morestack_noctxt(SB)
执行第12行的 delete,实际执行的是 runtime.mapdelete_fast64。
这些函数的参数类型是具体的 int64,mapdelete_fast64 跟原始的 delete 操作一样的,所以我们来看看 mapdelete。
mapdelete
长图预警!!!

大致代码分析如上,具体代码就留给大家去阅读了。其实大致过程:
写保护,防止并发写
查询要删除的
key是否存在存在则对其标志做删除标记
count--
所以你在大面积删除 key ,实际 map 存储的 key 是不会删除的,只是标记当前的key状态为 empty。
其实出发点,和 MySQL 的标记删除类似,防止后续会有相同的 key 插入,省去了扩缩容的操作。
但是这个对有些场景是不妥的,如果开发者在未来时间内都不会再插入相同的 key ,很可能会导致 OOM。
所以针对以上情况,go-zero 开发了 safemap 。下面我们看看 safemap 是如何避免这个问题的?
safemap
直接从操作 safemap 中分析为什么要这么设计:

预设一个 删除阈值,如果触发会放到一个新预设好的
newmap中两个
map是一个整体,所以key只能留一份
所以为什么要设置两个 map 就很清楚了:
dirtyOld作为存储主体,如果delete操作达到阈值,则会触发迁移。dirtyNew作为暂存体,会在到达阈值时,存放部分key/value
所以在迁移操作时,我们需要做的就是:将原先的 dirtyOld 清空,存储的 key/value 通过 for-range 重新存储到 dirtyNew,然后将 dirtyNew 指向 dirtyOld。
可能会有疑问:不是说
key/value没有删除吗,只是标记了tophash=empty其实在
for-range过程中,会过滤掉tophash <= emptyOne的 key
这样就实现了不需要的 key 不会被加入到 dirtyNew,进而不会影响 dirtyOld。

这其实也就是垃圾回收的年老代和新生代的概念。
以上是“如何给Golang map做GC”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注创新互联行业资讯频道!
本文题目:如何给Golangmap做GC
网站URL:http://www.jxjierui.cn/article/ghgcog.html


咨询
建站咨询
