usage

  1. package main
  2. import (
  3. "sync"
  4. "fmt"
  5. )
  6. func main() {
  7. //开箱即用
  8. var sm sync.Map
  9. //store 方法,添加元素
  10. sm.Store(1,"a")
  11. //Load 方法,获得value
  12. if v,ok:=sm.Load(1);ok{
  13. fmt.Println(v)
  14. }
  15. //LoadOrStore方法,获取或者保存
  16. //参数是一对key:value,如果该key存在且没有被标记删除则返回原先的value(不更新)和true
  17. //不存在则store,返回该value 和false
  18. if vv,ok:=sm.LoadOrStore(1,"c");ok{
  19. fmt.Println(vv)
  20. }
  21. if vv,ok:=sm.LoadOrStore(2,"c");!ok{
  22. fmt.Println(vv)
  23. }
  24. //遍历该map,参数是个函数,该函数参的两个参数是遍历获得的key和value,返回一个bool值,
  25. //当返回false时,遍历立刻结束。
  26. sm.Range(func(k,v interface{})bool{
  27. fmt.Printf("%v:%v \n",k,v)
  28. return true
  29. })
  30. }

简介

  • sync.Map 和普通的 map[interface{}]interfae{} 使用起来很像,但是保证了线程安全。
  • sync.Map 是某些场景下专用的,比如:
    • 一个 key 只写入一次,但是需要被多次读取。(读多写少)
    • 多个 goroutine 同时读取、写入和覆盖一系列不想交的 key 时。
  • 在以上两类情况下,和单独加读写锁的内置 map 相比,sync.Map 可以有效的减少锁竞争。

    设计思想

  • 通过冗余的两个 map,牺牲空间换时间。

  • 两个 map 的 value 类型都为 *interface{},所以可以同时进行修改。
  • read map 是原子类型的,自动更新,用于读操作,无需加锁;dirty map 含有最新数据并且所有操作都要使用 mutex。
  • 读取数据首先在 read 上读,如果没读到就去 dirty 上读,记录一个未命中数,当未命中数到达一定数量时,使用 dirty 的数据更新 read。

为什么 read map 是原子类型的?
因为如果并发的

稍加思索可以得出这样设计的好处是:

  1. 读操作无需获取和释放锁;

  2. 实现

    Map

    1. type Map struct {
    2. mu Mutex
    3. read atomic.Value // 只读类型,自动更新
    4. // dirty 的使用需要加锁
    5. dirty map[interface{}]*entry
    6. // 从 Map 读取 entry 时,如果 read 未命中将会从 dirty 中读取,此时 misses++
    7. // 当 misses 等于 dirty 的长度时,避免命中率过低,dirty 将升级为 read
    8. misses int
    9. }

    readOnly

    1. // readOnly 自动存储 dirty map 更新的数据,不能手动改变
    2. type readOnly struct {
    3. m map[interface{}]*entry
    4. amended bool // 如果 dirty 与 read 中的不一致则为 true
    5. }

    entry

    1. type entry struct {
    2. p unsafe.Pointer // map 的 value 为指针类型
    3. }

    load

    1. func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    2. // 从 read 中读取只读 map
    3. read, _ := m.read.Load().(readOnly)
    4. e, ok := read.m[key]
    5. // 如果 read 中没有,且 read 和 dirty 不一致
    6. if !ok && read.amended {
    7. m.mu.Lock()
    8. // 双检查,避免获取锁的时候 dirty 被其他线程升级为 read
    9. read, _ = m.read.Load().(readOnly)
    10. e, ok = read.m[key]
    11. if !ok && read.amended {
    12. e, ok = m.dirty[key]
    13. m.missLocked() // miss++,到达限额 dirty 提升为 read
    14. }
    15. m.mu.Unlock()
    16. }
    17. if !ok {
    18. return nil, false
    19. }
    20. return e.load()
    21. }

    dirty 升级为 read 过程

    1. func (m *Map) missLocked() {
    2. m.misses++
    3. if m.misses < len(m.dirty) {
    4. return
    5. }
    6. // misses 长度增长到等于 dirty 时,将 read 中的内容原子更新为 dirty
    7. m.read.Store(readOnly{m: m.dirty})
    8. m.dirty = nil
    9. m.misses = 0
    10. }