go语言safemap Go语言中文网

go语言的map多协程访问时需要加锁吗go语言的map多协程访问时需要加锁
支持==和!=操作就可以做key,实际上只有function、map、slice三个kind不支持作为key,因为只能和nil比较不能和另一个值比较 。布尔、整型、浮点、复数、字符串、指针、channel等都可以做key 。
struct能不能做key要看每一个字段,如果所有字段都可以做key , 那这个struct就可以 。有一个字段不能做key,这个struct就不能做key 。array也是,元素类型能做key,那这个array就可以 。
例如:
type Foo map[struct {
Bbool
Iint
Ffloat64
Ccomplex128
Sstring
P*Foo
Ch chan Foo
}]bool
每一个字段都可以做key , Foo就可以做key 。再如:
type Foo map[struct {
Fn func() Foo
Mmap[*Foo]int
S[]Foo
}]bool
有一个字段不能做key、Foo就不允许做key , 而这三个字段都不能 。
字段是递归检查的:
type Foo map[struct {
Sub struct {
M map[*Foo]bool
}
}]bool
Sub的M字段不能做key,Sub就不能做key,Foo也就不能做key 。
总之想把一个数据结构用于map的key,就不能包含function、map和slice 。
Go语言使用 map 时尽量不要在 big map 中保存指针 不知道你有没有听过这么一句:在使用 map 时尽量不要在 big map 中保存指针 。好吧 , 你现在已经听过了:)为什么呢?原因在于 Go 语言的垃圾回收器会扫描标记 map 中的所有元素 , GC 开销相当大,直接GG 。
这两天在《Mastering Go》中看到 GC 这一章节里面对比 map 和 slice 在垃圾回收中的效率对比,书中只给出结论没有说明理由,这我是不能忍的 , 于是有了这篇学习笔记 。扯那么多,Show Your Code
这是一个简单的测试程序 , 保存字符串的 map 和 保存整形的 map GC 的效率相差几十倍 , 是不是有同学会说明明保存的是 string 哪有指针?这个要说到 Go 语言中 string 的底层实现了,源码在 src/runtime/string.go里,可以看到 string 其实包含一个指向数据的指针和一个长度字段 。注意这里的是否包含指针 , 包括底层的实现 。
Go 语言的 GC 会递归遍历并标记所有可触达的对象 , 标记完成之后将所有没有引用的对象进行清理 。扫描到指针就会往下接着寻找,一直到结束 。
Go 语言中 map 是基于 数组和链表 的数据结构实现的,通过 优化的拉链法 解决哈希冲突,每个 bucket 可以保存8对键值,在8个键值对数据后面有一个 overflow 指针 , 因为桶中最多只能装8个键值对,如果有多余的键值对落到了当前桶 , 那么就需要再构建一个桶(称为溢出桶),通过 overflow 指针链接起来 。
因为 overflow 指针的缘故,所以无论 map 保存的是什么,GC 的时候就会把所有的 bmap 扫描一遍,带来巨大的 GC 开销 。官方 issues 就有关于这个问题的讨论,runtime: Large maps cause significant GC pauses #9477
无脑机翻如下:
如果我们有一个map [k] v , 其中k和v都不包含指针,并且我们想提高扫描性能,则可以执行以下操作 。
将“ allOverflow [] unsafe.Pointer”添加到 hmap 并将所有溢出存储桶存储在其中 。然后将 bmap 标记为noScan 。这将使扫描非常快 , 因为我们不会扫描任何用户数据 。
实际上,它将有些复杂,因为我们需要从allOverflow中删除旧的溢出桶 。而且它还会增加 hmap 的大小 , 因此也可能需要重新整理数据 。
最终官方在 hmap 中增加了overflow相关字段完成了上面的优化,这是具体的commit地址 。
下面看下具体是如何实现的,源码基于 go1.15,src/cmd/compile/internal/gc/reflect.go 中

推荐阅读