Golang源码阅读笔记 - Sync.Map

sync.Map 底层数据结构

// sync.map底层数据结构
type Map struct {
	mu Mutex
	read atomic.Value
	dirty map[interface{}]*entry
	misses int
}

// read数据结构
type readOnly struct {
	m       map[interface{}]*entry
	amended bool // true if the dirty map contains some key not in m.
}

// map中value的载体
type entry struct {
	p unsafe.Pointer // *interface{}
}

var expunged = unsafe.Pointer(new(interface{}))
  1. sync.Map底层是Map结构体,包含四个元素:

    • mu: 在更新readdirty时使用并发锁
    • read: 是一个只读readOnly结构,sync.Map的主要读操作从read获取
    • dirty: 一个map,当数据变更时,优先变更dirty结构,并在适当的时机同步read
      • 但是当要更新一个已存在的key时,会直接更新entry对应的pointer指向(此时相当于read和dirty同时修改了)
    • misses: 记录从read中取不到数据的次数(决定是否需要将dirty复制给read); read每次更新后置0
  2. read底层是一个atomic.Value, 存储readOnly结构体

    • m: read里存储数据的map
    • amended: 布尔值,标记dirty map是否有元素没同步到read.m
  3. sync.mapvalue值是放在entry结构体中存储的

    • p记录value的指针
  4. expunged: 一个特殊的变量,标记dirty map中已经被删除的entry

  5. sync.atomic.Value

    • atomic.Value底层是一个包含interface{}的结构体

      type Value struct {
      	v interface{}
      }
      
    • atomic.Value提供原子读写的能力;主要支持对int32 int64 uint32 uint64 uintptr unsafe.Pointeradd swap compareAndSwap操作

    • sync/atomic/doc.go对这些操作做了简要介绍,功能和函数名差不多;官方不建议应用层直接使用atomic.Value,推荐使用channel或者sync包中的其他工具进行数据同步

Store

func (m *Map) Store(key, value interface{}) {
	read, _ := m.read.Load().(readOnly)
	// 如果key在read.m中已存在,且对应entry没有被标记删除,直接更新entry的指针
	// 此时相当于read.m和dirty同时更新了
	if e, ok := read.m[key]; ok && e.tryStore(&value) {
		return
	}

	m.mu.Lock()
	read, _ = m.read.Load().(readOnly)
	// read.m中存在这个key,但被标记删除:
	// 	 1. 去除entry"被删除标记", entry.p, expunged => nil 
	// 	 2. 将entry赋值给dirty
	//	 3. 将value值写入entry
	if e, ok := read.m[key]; ok {
		if e.unexpungeLocked() {
			m.dirty[key] = e
		}
		e.storeLocked(&value)
	// read.m中没有这个key,但dirty中有这个key
	// 	  1. 将value写给dirty中的entry
	} else if e, ok := m.dirty[key]; ok {
		e.storeLocked(&value)
	// read.m和dirty中都没有这个key
	// 	  1. 新建一个entry, 赋值value,并写入dirty
	// 	  2. 将read.amended设置为true, 表示read和dirty数据已经不一致了(这种情况没有新增到read.m)
	} else {
		if !read.amended {
			m.dirtyLocked()
			m.read.Store(readOnly{m: read.m, amended: true})
		}
		m.dirty[key] = newEntry(value)
	}
	m.mu.Unlock()
}

// 尝试将i对应值写入entry
func (e *entry) tryStore(i *interface{}) bool {
	for {
		p := atomic.LoadPointer(&e.p)
		// 如果entry被标记删除,返回写入false
		if p == expunged {
			return false
		}
		// 否则,将i的指针更新到entry.p
		if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
			return true
		}
	}
}

// 将i值写给entry.p
func (e *entry) storeLocked(i *interface{}) {
	atomic.StorePointer(&e.p, unsafe.Pointer(i))
}

Store操作总结:

  1. 如果是更新已存在的key,会直接更新对应entry的指针指向
    • 相当于同时修改read.mdirty,不会造成readdirty数据不一致
  2. 如果是新key,会优先写进dirty
    • 此时,read.mdirty数据不一致

Load

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
	read, _ := m.read.Load().(readOnly)
	// 先直接从read.m中取,如果已存在,直接返回
	e, ok := read.m[key]
	// read.m中没有,并且amended = true(表示dirty和read数据不一致,dirty可能有)
	// 	 1. 加锁。这一步加锁后重新从read.m中取,是防止加锁期间read.m被其他协程更新
	// 	 2. 尝试从dirty中获取
	// 	 3. 从read中miss的次数加一,并判断是否需要重建read
	if !ok && read.amended {
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
			e, ok = m.dirty[key]
			m.missLocked()
		}
		m.mu.Unlock()
	}
	if !ok {
		return nil, false
	}
	return e.load()
}

// missLocked即增加一次从read中取不到的次数
// 如果misses的次数比dirty的长度还大,则将dirty重写到read,misses 置零
func (m *Map) missLocked() {
	m.misses++
	if m.misses < len(m.dirty) {
		return
	}
	m.read.Store(readOnly{m: m.dirty})
	m.dirty = nil    // ??? 为啥把dirty改成nil???
	m.misses = 0
}

Load操作总结:

  1. 读操作优先从read中取数据,有则直接返回,没有则从dirty里取
    • 这里的逻辑和Store中的update互相验证
      • read中已有的key,一定是最新的;Store中的update刚好没有只更新dirty
  2. misses计数器记录从read.m中读取失败的次数,达到阈值,需要重建read.m

Delete

func (m *Map) Delete(key interface{}) {
	m.LoadAndDelete(key)
}

func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
	// read.m存在这个key,直接将entry.p 设置成nil
	// read.m不存在,且read和dirty数据不一致
	// 	 1. 从dirty中删除这个key
	// 	 2. misses计数 +1
	if !ok && read.amended {
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
			e, ok = m.dirty[key]
			delete(m.dirty, key)
			m.missLocked()
		}
		m.mu.Unlock()
	}
	if ok {
		return e.delete()
	}
	return nil, false
}

// entry的delete操作
// 	 如果entry.p 已经是nil,或者被标记删除,直接返回
// 	 否则,将entry.p 设置成nil
func (e *entry) delete() (value interface{}, ok bool) {
	for {
		p := atomic.LoadPointer(&e.p)
		if p == nil || p == expunged {
			return nil, false
		}
		if atomic.CompareAndSwapPointer(&e.p, p, nil) {
			return *(*interface{})(p), true
		}
	}
}

Delete操作总结:

  1. read.m里有,直接将entry.p设置为nil (dirty共用一个entry指针,dirty里数据也为nil)
  2. read.m里没有,从dirty中删除
  3. delete不会从read中直接删除key

Range

func (m *Map) Range(f func(key, value interface{}) bool) {
	read, _ := m.read.Load().(readOnly)
	
	// 如果read和dirty数据不一致,直接遍历dirty了
	if read.amended {
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		if read.amended {
			read = readOnly{m: m.dirty}
			m.read.Store(read)
			m.dirty = nil
			m.misses = 0
		}
		m.mu.Unlock()
	}

	for k, e := range read.m {
		v, ok := e.load()
		if !ok {
			continue
		}
		if !f(k, v) {
			break
		}
	}
}

Sync.Map的使用场景

sync.Map结构体的源码注释中,说明了sync.Map的使用场景:

// The Map type is optimized for two common use cases: (1) when the entry for a given
// key is only ever written once but read many times, as in caches that only grow,
// or (2) when multiple goroutines read, write, and overwrite entries for disjoint
// sets of keys. In these two cases, use of a Map may significantly reduce lock
// contention compared to a Go map paired with a separate Mutex or RWMutex.
  1. 写少读多
  2. 多goroutine并发读写

思考

  1. missLocked()函数中,如果达到阈值会将dirty拷贝给read.m, 为什么要把dirty设置为nil?如此还怎么保证dirty的数据一定包含read.m呢?

你可能感兴趣的:(go,golang)