• golang map 并发读写 sync.Map 实现原理


    一. map并发读写不安全

    Go  map 是不支持并发写操作的,当 Goroutine 操作同一个 map,会产生报错:fatal error: concurrent map writes

    1. map 并发读写 

    1. //map 并发读写 产生错误
    2. //fatal error: concurrent map read and map write
    3. func MapConcurrentErr() {
    4. c := make(map[string]int)
    5. go func() { //开一个goroutine写map
    6. for j := 0; j < 1000000; j++ {
    7. c[fmt.Sprintf("%d", j)] = j
    8. }
    9. }()
    10. go func() { //开一个goroutine读map
    11. for j := 0; j < 1000000; j++ {
    12. fmt.Println(c[fmt.Sprintf("%d", j)])
    13. }
    14. }()
    15. time.Sleep(time.Second * 20)
    16. }

    2.使用sync map 解决并发安全问题

    1. //sync map 并发读写安全
    2. func SyncMapConcurrent() {
    3. c := sync.Map{}
    4. go func() { //开一个goroutine写map
    5. for j := 0; j < 1000000; j++ {
    6. c.Store(j, j)
    7. fmt.Println("store", j)
    8. }
    9. }()
    10. go func() { //开一个goroutine读map
    11. for j := 0; j < 1000000; j++ {
    12. load, ok := c.Load(j)
    13. fmt.Println("load", load, ok)
    14. }
    15. }()
    16. time.Sleep(time.Second * 20)
    17. }


    二.sync map实现

    2.1 结构体

    1.Map结构体

    1. type Map struct {
    2. //锁,用于保护 dirty 和 read。读写dirty、将dirty copy到 read时需要持有该锁。
    3. mu Mutex
    4. //实际结构为 readOnly
    5. // read contains the portion of the map's contents that are safe for
    6. // concurrent access (with or without mu held).
    7. //
    8. // The read field itself is always safe to load, but must only be stored with
    9. // mu held.
    10. //
    11. // Entries stored in read may be updated concurrently without mu, but updating
    12. // a previously-expunged entry requires that the entry be copied to the dirty
    13. // map and unexpunged with mu held.
    14. read atomic.Value // readOnly
    15. // dirty contains the portion of the map's contents that require mu to be
    16. // held. To ensure that the dirty map can be promoted to the read map quickly,
    17. // it also includes all of the non-expunged entries in the read map.
    18. //
    19. // Expunged entries are not stored in the dirty map. An expunged entry in the
    20. // clean map must be unexpunged and added to the dirty map before a new value
    21. // can be stored to it.
    22. //
    23. // If the dirty map is nil, the next write to the map will initialize it by
    24. // making a shallow copy of the clean map, omitting stale entries.
    25. dirty map[interface{}]*entry
    26. //misses 字段来统计 read 被穿透的次数(被穿透指需要读 dirty 的情况),超过一定次数则将
    27. //dirty 数据同步到 read 上
    28. // misses counts the number of loads since the read map was last updated that
    29. // needed to lock mu to determine whether the key was present.
    30. //
    31. // Once enough misses have occurred to cover the cost of copying the dirty
    32. // map, the dirty map will be promoted to the read map (in the unamended
    33. // state) and the next store to the map will make a new dirty copy.
    34. misses int
    35. }

    2.readOnly

    1. // readOnly is an immutable struct stored atomically in the Map.read field.
    2. type readOnly struct {
    3. m map[interface{}]*entry
    4. //标记ditry中是否包含元素
    5. amended bool // true if the dirty map contains some key not in m.
    6. }

     3.entry
    entry 用来存储值的地址属性 p 有三种状

    • p == nil: 键值已经被删除,且 m.dirty == nil
    • p == expunged: 键值已经被删除,但 m.dirty!=nilm.dirty 不存在该键值(expunged 实际是空接口指针)
    • 否则键值对存在,存在于 m.read.m 中,如果 m.dirty!=nil 则也存在于 m.dirty
    1. // expunged is an arbitrary pointer that marks entries which have been deleted
    2. // from the dirty map.
    3. var expunged = unsafe.Pointer(new(interface{}))


     2.2 map结构体的方法

    1.Load

    Load方法用于获取map中的值

    1. // Load returns the value stored in the map for a key, or nil if no
    2. // value is present.
    3. // The ok result indicates whether value was found in the map.
    4. func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    5. read, _ := m.read.Load().(readOnly)
    6. e, ok := read.m[key]
    7. if !ok && read.amended {
    8. m.mu.Lock()
    9. // Avoid reporting a spurious miss if m.dirty got promoted while we were
    10. // blocked on m.mu. (If further loads of the same key will not miss, it's
    11. // not worth copying the dirty map for this key.)
    12. read, _ = m.read.Load().(readOnly)
    13. e, ok = read.m[key]
    14. if !ok && read.amended {
    15. e, ok = m.dirty[key]
    16. //触发miss逻辑, 符合条件时会将dirty数据copy 到readOnly
    17. // Regardless of whether the entry was present, record a miss: this key
    18. // will take the slow path until the dirty map is promoted to the read
    19. // map.
    20. m.missLocked()
    21. }
    22. m.mu.Unlock()
    23. }
    24. if !ok {
    25. return nil, false
    26. }
    27. // 读取entry中值
    28. return e.load()
    29. }


    2.Store

    store用户设置key的值

    1. // Store sets the value for a key.
    2. func (m *Map) Store(key, value interface{}) {
    3. read, _ := m.read.Load().(readOnly)
    4. if e, ok := read.m[key]; ok && e.tryStore(&value) {
    5. return
    6. }
    7. m.mu.Lock()
    8. read, _ = m.read.Load().(readOnly)
    9. if e, ok := read.m[key]; ok {
    10. if e.unexpungeLocked() {
    11. // The entry was previously expunged, which implies that there is a
    12. // non-nil dirty map and this entry is not in it.
    13. m.dirty[key] = e
    14. }
    15. e.storeLocked(&value)
    16. } else if e, ok := m.dirty[key]; ok {
    17. e.storeLocked(&value)
    18. } else {
    19. if !read.amended {
    20. // We're adding the first new key to the dirty map.
    21. // Make sure it is allocated and mark the read-only map as incomplete.
    22. m.dirtyLocked()
    23. m.read.Store(readOnly{m: read.m, amended: true})
    24. }
    25. m.dirty[key] = newEntry(value)
    26. }
    27. m.mu.Unlock()
    28. }



    3.Delete
     

    1. // Delete deletes the value for a key.
    2. func (m *Map) Delete(key interface{}) {
    3. m.LoadAndDelete(key)
    4. }
    1. // LoadAndDelete deletes the value for a key, returning the previous value if any.
    2. // The loaded result reports whether the key was present.
    3. func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
    4. read, _ := m.read.Load().(readOnly)
    5. e, ok := read.m[key]
    6. if !ok && read.amended {
    7. m.mu.Lock()
    8. read, _ = m.read.Load().(readOnly)
    9. e, ok = read.m[key]
    10. if !ok && read.amended {
    11. e, ok = m.dirty[key]
    12. delete(m.dirty, key)
    13. // Regardless of whether the entry was present, record a miss: this key
    14. // will take the slow path until the dirty map is promoted to the read
    15. // map.
    16. m.missLocked()
    17. }
    18. m.mu.Unlock()
    19. }
    20. if ok {
    21. return e.delete()
    22. }
    23. return nil, false
    24. }

    三 总结 

    • 通过 read 和 dirty 两个字段将读写分离,读的数据存在只读字段 read 上,将最新写入的数据则存在 dirty 字段上
    • 读取时会先查询 read,不存在再查询 dirty,写入时则只写入 dirty
    • 读取 read 并不需要加锁,而读或写 dirty 需要加锁,cory dirty到read时需要加锁
    • 另外有 misses 字段来统计 read 被穿透的次数(被穿透指需要读 dirty 的情况),超过一定次数则将 dirty 数据同步到 read 上
    • 对于删除数据则直接通过标记来延迟删除

  • 相关阅读:
    JupyterNotebook设置Python环境的方法步骤
    KubeVela 插件指南:轻松扩展你的平台专属能力
    软件测试员的悲哀竟是...自己的技术能力不能满足大厂要求?
    数据结构手写算法整理(考研)
    docker部署前端项目(二)遇到的问题
    软硬件基础知识学习--小日记(1)
    如何提升数据质量
    JavaScript的面向对象
    【电商项目实战】拦截器(详细篇)
    市面主流的Web大前端框架以及特性
  • 原文地址:https://blog.csdn.net/qq_16399991/article/details/127111389