
golang源码:https://github.com/golang/go
commit c62099cfac6b0fd46efbdab7205bb17597096472
这里使用最新的 master 代码进行学习,与时俱进;
提前安装好 go 的开发环境,下载好 go 的源码,如果这些都不会的话,这边建议先不着急学习源码;
go version go1.24.3 windows/amd64
开发环境:VSCODE,方便代码跳转;
废话不多说,直接上干货,我知道程序员都是比较性急的生物;
本节学习的代码路径:src\sync\map.go
E:\gowork\src\github.com\golang\go\src\sync\map.go sync 包是 Go 标准库中的一个基础并发原语包,提供了多种用于并发控制的结构和原语,比如互斥锁(Mutex)、等待组(WaitGroup)、一次性执行(Once)、条件变量(Cond)等。
type Map struct {
// go vet 工具会检查当前的结构体实例是否被复制,编译器会忽略该字段
_ noCopy
mu Mutex
read atomic.Pointer[readOnly]
dirty map[any]*entry
misses int
}1)Map
与 map[any]any,sync 包下的 Map 是并发安全的,无需额外的加锁和解锁操作,实际 Map 结构体中是自带自旋锁的;
下面对每个字段进行详细的介绍:
read 字段:包含 map 的部分内容,主要用于加速读取性能;典型的用空间换时间的做法;
atomic.Pointer[any]:src\sync\atomic\type.go 下的
type Pointer[T any] struct {} 结构体,当前只需要了解,这个 Pointer 结构体提供原子操作 T 类型的值的方法,后续会在 atomic 源码进行详细的讲解;
也就是 read 是一个原子读取 readOnly 类型的变量;那么 readOnly 又长啥样呢?接着往下看:
type readOnly struct {
m map[any]*entry
amended bool // true if the dirty map contains some key not in m.
}在 readOnly 结构体中,只有两个字段:
dirty 字段:包含了 map 中需要加锁才能访问的内容,当 read map 的命中率变低时,会将 dirty 整体提升为 read map,提高读取的性能;
misses 字段: 这个字段比较好理解,读取的时候,没有命中 read 的次数,也就是取 dirty 中读取的次数;
在 entry 结构体中:
type entry struct {
p atomic.Pointer[any]
}p 指向该条目(entry)中存储的 interface{} 类型的值。
func newEntry(i any) *entry {
e := &entry{}
e.p.Store(&i)
return e
}func newEntry() 函数提供创建 entry 实例,原子存储 value;这个原子存储的功能是 atomic.Pointer 结构体 Store() 方法提供的能力;
entry 是 Map 内部定义的结构体,是保存 Map 中的 value 的地方,entry 提供的方法列表:
func (e *entry) load() (value any, ok bool)
func (e *entry) tryCompareAndSwap(old, new any) bool
func (e *entry) unexpungeLocked() (wasExpunged bool)
func (e *entry) swapLocked(i *any) *any
func (e *entry) tryLoadOrStore(i any) (actual any, loaded, ok bool)
func (e *entry) delete() (value any, ok bool)
func (e *entry) trySwap(i *any) (*any, bool)
func (e *entry) tryExpungeLocked() (isExpunged bool)Map 对外提供的方法列表:
func (m *Map) Load(key any) (value any, ok bool)
func (m *Map) Store(key, value any)
func (m *Map) Clear()
func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool)
func (m *Map) LoadAndDelete(key any) (value any, loaded bool)
func (m *Map) Delete(key any)
func (m *Map) Swap(key, value any) (previous any, loaded bool)
func (m *Map) CompareAndSwap(key, old, new any) (swapped bool)
func (m *Map) CompareAndDelete(key, old any) (deleted bool)
func (m *Map) Range(f func(key, value any) bool)Map 不对外提供的方法列表:
func (m *Map) loadReadOnly() readOnly
func (m *Map) missLocked()
func (m *Map) dirtyLocked()2)var expunged = new(any)
expunged 是一个特殊的指针,用于标记那些已经从 dirty map 中被删除的条目。
在 sync.Map 中,删除一个 key 不是立即从 map 中进行删除,是先将 entry 标记成哨兵指针(sentinel pointer);
3)Map 提供的方法
func (m *Map) loadReadOnly() readOnly {
if p := m.read.Load(); p != nil {
return *p
}
return readOnly{}
}loadReadOnly():不可导出方法,提供原子读取 Map 中的 read 字段的值;
func (m *Map) missLocked() {
m.misses++
if m.misses < len(m.dirty) {
return
}
m.read.Store(&readOnly{m: m.dirty})
m.dirty = nil
m.misses = 0
}missLocked():方法提供对 misses 计数,对于没有命中 read 的次数进行计数
当 misses 的值比 dirty 的长度相等或大于的时候,会重新将 dirty 提升到 read 中,提高读取的性能;
然后将 m.dirty 的值置为 nil;
那么接下来看下 dirtyLocked() 方法:
func (m *Map) dirtyLocked() {
if m.dirty != nil {
return
}
read := m.loadReadOnly()
m.dirty = make(map[any]*entry, len(read.m))
for k, e := range read.m {
if !e.tryExpungeLocked() {
m.dirty[k] = e
}
}
}dirtyLocked():如果 m.dirty 为空,根据 read.m 的长度创建 m.dirty,实际 dirty 的长度可能比 read.m 小,毕竟有部分已经删除的 key 不会再保存到 m.dirty 中;
那我们继续看下 tryExpungeLocked() 方法:
func (e *entry) tryExpungeLocked() (isExpunged bool) {
p := e.p.Load()
for p == nil {
if e.p.CompareAndSwap(nil, expunged) {
return true
}
p = e.p.Load()
}
return p == expunged
}tryExpungeLocked():可以看到,首先读取 p 的值,如果 p 的值为空,就会将 expunged 哨兵指针赋值给 p,并返回 true;这个函数会判断 key 对应的 value 是否是 expunged,如果为 nil,则设置成 expunged,非空则返回 false;
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。