上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
map
只在两种情况下,可减少锁争用,提升性能。
- 少写多读。
- 多个并发读写不同项。
源码剖析
快慢分离, read
以 atomic 操作, dirty
需 mutex 锁定。相比读写锁(rwmutex)写操作锁定全局,此设计有更好性能。
查找优先选择 read
,失败后再以慢路径从 dirty
找。不直接存储值,而是以 entry.p
引用,故可用原子操作修改。至于删除也不过是 entry.p = nil
,不影响字典本身。
慢操作累加 misses
,达到阈值时,以 dirty
替换 read
字典。然后按需重建 dirty
字典,并复制 read
里所有非删除项。
// sync/map.go type Map struct { mu Mutex read atomic.Value // readOnly dirty map[any]*entry misses int }
type readOnly struct { m map[any]*entry // true if the dirty map contains some key not in m. amended bool } type entry struct { p unsafe.Pointer }
更新
优先从 read
查找,以原子操作修改 entry.p
进行值替换。或加锁,进入慢路径,尝试更新 dirty
字典。
func (m *Map) Store(key, value any) { // 尝试 read 快速更新。 read, _ := m.read.Load().(readOnly) if e, ok := read.m[key]; ok && e.tryStore(&value) { return } // 更新失败,进入慢路径。 m.mu.Lock() // 再次检查 read 字典。 read, _ = m.read.Load().(readOnly) if e, ok := read.m[key]; ok { // 如已被标记为删除,在 dirty 更新。 if e.unexpungeLocked() { m.dirty[key] = e } // 更新值引用(e.p = &value)。 e.storeLocked(&value) } else if e, ok := m.dirty[key]; ok { // 更新 dirty 字典。 e.storeLocked(&value) } else { // 在 dirty 字典插入。 // 如果 !amended,表示 read 包含全部数据。 // 只能是 dirty 不存在,比如替换 read.m 字典。 if !read.amended { // 新建 dirty,复制 read 数据。 m.dirtyLocked() // 设置 read.amended = true,表示有数据仅存于 dirty。 m.read.Store(readOnly{m: read.m, amended: true}) } m.dirty[key] = newEntry(value) } m.mu.Unlock() }
func (m *Map) dirtyLocked() { if m.dirty != nil { return } read, _ := m.read.Load().(readOnly) // 新建,复制所有非删除项。 m.dirty = make(map[any]*entry, len(read.m)) for k, e := range read.m { if !e.tryExpungeLocked() { m.dirty[k] = e } } } func (e *entry) tryExpungeLocked() (isExpunged bool) { p := atomic.LoadPointer(&e.p) // 没有 value,删除项。 for p == nil { // 将 nil 置为删除标记。 if atomic.CompareAndSwapPointer(&e.p, nil, expunged) { return true } p = atomic.LoadPointer(&e.p) } return p == expunged }
读取
慢操作导致 miss
计数增加。一旦超过阈值,则用 dirty
替换掉 read.m
字典。
func (m *Map) Load(key any) (value any, ok bool) { // 优先从 read 读。 read, _ := m.read.Load().(readOnly) e, ok := read.m[key] // 失败,且有 dirty 字典。 if !ok && read.amended { m.mu.Lock() // 再次尝试 read 字典。 read, _ = m.read.Load().(readOnly) e, ok = read.m[key] // 失败,从 dirty 读。 if !ok && read.amended { e, ok = m.dirty[key] // 累加 miss 计数。 m.missLocked() } m.mu.Unlock() } if !ok { return nil, false } return e.load() }
func (m *Map) missLocked() { m.misses++ // 检查阈值。 if m.misses < len(m.dirty) { return } // 用 dirty 替换 read.m 字典。 m.read.Store(readOnly{m: m.dirty}) // 重置状态。 m.dirty = nil m.misses = 0 }
删除
删除只是设置 entry.p = nil
,新建 dirty
进行数据复制时会将其排除。
func (m *Map) Delete(key any) { m.LoadAndDelete(key) } func (m *Map) LoadAndDelete(key any) (value any, loaded bool) { read, _ := m.read.Load().(readOnly) e, ok := read.m[key] // 失败,且 dirty 字典存在。 if !ok && read.amended { m.mu.Lock() // 重试 read 字典。 read, _ = m.read.Load().(readOnly) e, ok = read.m[key] if !ok && read.amended { e, ok = m.dirty[key] delete(m.dirty, key) // 同样会累加 miss 计数。 m.missLocked() } m.mu.Unlock() } // 删除(e.p = nil)。 if ok { return e.delete() } return nil, false }
func (e *entry) delete() (value any, ok bool) { for { p := atomic.LoadPointer(&e.p) if p == nil || p == expunged { return nil, false } // 设置 e.p = nil,释放 value。 if atomic.CompareAndSwapPointer(&e.p, p, nil) { return *(*any)(p), true } } }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论