- 在现有标准库下,为什么需要添加sync.Map
- sync.Map的原理
package RegularIntMap
type RegularIntMap struct {
sync.RWMutex
internal map[int]int
}
func NewRegularIntMap() *RegularIntMap {
return &RegularIntMap{
internal: make(map[int]int),
}
}
func (rm *RegularIntMap) Load(key int) (value int, ok bool) {
rm.RLock()//读锁,可并发
result, ok := rm.internal[key]
rm.RUnlock()
return result, ok
}
func (rm *RegularIntMap) Delete(key int) {
rm.Lock()
delete(rm.internal, key)
rm.Unlock()
}
func (rm *RegularIntMap) Store(key, value int) {
rm.Lock()
rm.internal[key] = value
rm.Unlock()
}
但哪怕使用了读写分离锁,当代码运行在多核CPU下(通常情况下是超过8/16核以上的服务器)性能依然堪忧。因为以下几个原因:
- reflect.New很慢(map类型安全的底层是通过反射机制来实现的)
- sync.RWMutex很慢
- atomic.AddUint32很慢
- 所有的cpu都在读写同一个内存地址
type mapInterface interface {
//类似于java的Map.get(),返回命中key的value,并带有一个bool表示是否命中
Load(interface{}) (interface{}, bool)
//类似于java的Map.set(),写入key-value
Store(key, value interface{})
//尝试从map中拉取key-value,如果不存在,则写入key-value。loaded表示是否load操作命中
LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
//删除一个key-value
Delete(interface{})
//遍历所有的key-value调用func,func应该返回一个bool值。func返回false会导致遍历终止
Range(func(key, value interface{}) (shouldContinue bool))
}
func syncMapUsage() {
fmt.Println("sync.Map test (Go 1.9+ only)")
fmt.Println("----------------------------")
// Create the threadsafe map.
var sm sync.Map
// Fetch an item that doesn't exist yet.
result, ok := sm.Load("hello")
if ok {
fmt.Println(result.(string))//注意这里,sync.Map容器是以interface{}的方式保存对象的,所以需要进行类型转换。也就是说sync.Map是类型不安全的
} else {
fmt.Println("value not found for key: `hello`")
}
// Store an item in the map.
sm.Store("hello", "world")
fmt.Println("added value: `world` for key: `hello`")
// Fetch the item we just stored.
result, ok = sm.Load("hello")
if ok {
fmt.Printf("result: `%s` found for key: `hello`\n", result.(string))
}
fmt.Println("---------------------------")
}
总结一下,sync.Map像是一个不够完善的容器,比起已有的map主要存在以下不足:
- 低并发情况下的性能不足
- 冗余数据(两个不同的map,后面会谈到)
- 缺少类型安全控制
- 有限的api。比如不支持len操作



最后是删除操作,分两种情况:
- 删除的key仅在dirty中存在。此时只需要简单的将key从dirty内部的map中删除即可。
- 删除的key在read中存在。这种情况下会先把read内部map中对应key的值设为expunged(一个指针标记),但不会对dirty做任何操作。此时因为read中对应的key依然存在(仅仅是value=expunged),所以针对该key的任何读写操作依然有效(读操作遇到expunged的值会返回nil)。直到misses达到阈值,dirty往read进行迁移的时候,才会判断value为expunged的key放弃迁移,使之失效。