map的结构体定义

type Map struct {
  //此锁是为了维护Map的dirty数据
  mu Mutex
  //用来存读的数据,只对类型,不会造成读写抵触
  read atomic.Value 
  dirty map[interface{}] *entry
  //当读数据时分,该字段不在read中,测验从dirty中读取,不论是否在dirty中读取到数据,missed+1
  //当累计到len(dirty) 时,会将dirty拷贝到read,并将dirty清空,以此提高读功能
  missed int
}

在sync.Map中用到了冗余数据结构read、dirty。其间read的类型是atomic.Value,它会经过atomic.Value的Load办法将其断语为readOnly目标,因而read的实际类型为readOnly

read, _ := m.read.Load().(readOnly)

readOnly的数据结构如下

type readOnly struct {
  m map[interface{}]*entry
  //当sync.Map.dirty中包含了某些不存在的key时,amend的值为true
  amended bool
}
  • amend属性的作用是志明dirty中是否有readOnly.m中未包含的数据,因而当对sync.Map的读操作在read中找不到数据时分,将进一步在dirty中查找

  • readOnly.m和Map.dirty中map存储的值类型是*entry,它包含一个指针p,指向存储的value值。

type entry struct {
  p unsafe.Pointer
}

entry.p的值有三种类型

  • nil:entry现已被删去,且m.dirty为nil
  • expunged:entry被删去,m.dirty不为nil,但entry不存在m.dirty中
  • 其他:entry有效,记录在m.read中,若dirty不为空,也会记录在dirty中

虽然read和dirty存在冗余数据,可是这些数据entry是经过指针指向的,因而,尽管map的value可能会很大,可是空间存储还是足够。

办法

sync.Map有四个办法完成,分别是Load、Store、Delete和Range

Load

加载办法,经过提供的key,查找对应的值value

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
  //从m的read中经过Load办法得到readOnly
  read, _ := m.read.Load().(readOnly)
  //从read的m中查找key
  e, ok := read.m[key]
  //假如在read中没有找到,且标明有新数据在dirty中
  //那么在dirty中查找
  if !ok &&read.amended {
    m.mu.Lock()
    //两层检查防止在本次加锁的时分,有其他的goroutine正好将map中的dirty数据复制到了read中
    //Map.read的并发安全性确保就在他的修正是经过原子操作的,因而需要再一次的load
    read, _ = m.read.Load().(readOnly)
    e, ok := read.m[k]
    if !ok && read.amended {
      e, ok := m.dirty[key]
      //不论是否从dirty中找到数据,都要讲missed加一
      m.missLocked()
    }
    m.mu.Unlock()
  }
  if !ok {
    return nil, false
  }
  //经过map的load办法,将entry.p加载为对应的指针,再回来指针指向的值
  return e.load()
}

Map的missLocked函数是确保sync.Map功能的重要函数,他的目的是将存在有所的dirty中的数据,转移导只读现成安全的read中去

func (m *Map) missLocked() {
  m.misses++
  if m.misses < len(m.dirty) {
    return
  }
  //将dirty复制到read中
  m.read.Store(readOnly{m: m.dirty})
  m.dirty = nil //将dirty清空
  m.misses = 0  //计数清零
}

Store

该办法用于更新或许新增键值对key-value

func (m *Map) Store(key,value interface{}) {
  //假如read中存在该兼职,且兼职美哦与被删去,则测验直接存储
  read, _ := m.read.Load().(readOnly)
  if e, ok := read.m[key]; ok && e.tryStore(&value) {
    return
  }
  //假如不满足上述条件
  m.mu.Lock()
  read, _ = m.read.Load().(readOnly)
  if e, ok := read.m[key]; ok {
    //判别entry是否被符号删去
    if e.unexpungeLocked() {
      //假如entry被符号删去,则将entry参加导dirty中
      m.dirty[key] = e
    }
    //更新entry指向的value地址
    e.storeLocked(&value)
  }else if e,ok := m.dirty[key];ok { //dirty中有该键值,则更新
    e.storeLocked(&value)
  }else{ //dirty和read中均无该键值
    if !read.amended {
      m.dirtyLocked() //从m.read中复制未删去的数据导dirty中
      m.read.Store(readOnly{m:read.m, amended:true})
    }
    //将entry添加到dirty中
    m.dirty[key] = newEntry(value)
  }
}

Store每次的操作都是从read开始的,先不满足条件时,才加锁操作dirty。可是因为存在从read中复制数据的状况,当m.read中数据量很大的状况,会对功能造成影响。

Delete

删去某个键值

func (m *Map) Delete(key interface{}) {
  read,_ := m.read.Load().(readOnly)
  e, ok := read.m[key]
  if !ok && read.amended {
    m.mu.Lock()
    read, _ = m.read.Load().(readOnly)
    e, ok := read.m[key]
    if !ok && read.amended {
      delete(m.dirty,key)
    }
    m.mu.Unlock()
  }
  if ok {
    e.delete()
  }
}
func (e *entry) delete()(hadValue bool) {
  for {
    p := atomic.LoadPointer(&e.p)
    //假如p指针为空,或许被符号清除
    if p == nil || p ==  expunged {
      return false
    }
    //经过原子操作将e.p符号为nil
    if atomic.CompareAndSwapPointer(&e.p, p, nil{
      return true
    }
  }
}

Delete中的逻辑和Store逻辑相似,都是从read开始,假如这个key不在read中,且dirty中有新的数据,则加锁从dirty中删去,而且需要做两层检查。

Range

想要遍历sync.Map,不能经过for range的形式,因而他本身提供了Range办法,经过回调方法遍历

func (m *Map) Range(f func(key, value interface{}) bool) {
  read, _ := m.read.Load().(readOnly)
  if read.amended {
    m.mu.Lock()
    read, _ = m.read.Load().(readOnly)
    if read.amended  {
      //将dirty复制到read中
      read = readOnly{m:m.dirty}
      m.read.Store(read)
      m.dirty = nil
      m.misses = 0
    }
    m.mu.Unlock()
  }
  //遍历整理过后的read
  for k,e := range read.m {
    v, ok := e.load()
    if !ok {
      continue
    }
    if !f(k,v) {
      break
    }
  }
}

sync.Map总结

  • 空间换时刻:经过两个冗余的数据结构(read、dirty),削减锁对功能的影响
  • 读操作运用read,防止读写抵触
  • 动态调整,经过misses值,防止dirty数据过多
  • 双检查机制:防止在非原子操作是产生过错
  • 延迟删去机制:删去一个键值仅仅先打符号,只有等提高dirty(复制到read中,并清空本身)时才清理删去的数据。
  • 优先从read中读、改和删去,因为对read操作不用加锁,大大提高功能。

运用示例

func main() {
  var sm sync.Map
  sm.Store(1,"a")
  sm.Store(2,"b")
  sm.Store("c",3)
  if v, ok := sm.Load("c"); ok {
    fmt.Println(v)
  }
  //删去
  sm.Delete(1)
  //遍历
  //参数func中的参数是遍历取得的key和value,回来一个bool值
  sm.Range(func(key,value interface{}) bool {
    fmt.Println(key, value)
    return true
  })
}