Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深入理解Golang sync.Map设计与实现

深入理解Golang sync.Map设计与实现

原创
作者头像
路之遥
发布于 2023-09-14 07:26:46
发布于 2023-09-14 07:26:46
77600
代码可运行
举报
文章被收录于专栏:luzhiyaoluzhiyao
运行总次数:0
代码可运行

Golang 为并发编程提供了多种并发原语(Mutex、RWMutex、sync.Map),用于临界区的数据访问和保护;开发应用时,面对不同的场景如何选择合适的并发原语,使功能正常实现的同时提供更高的性能;在互联网应用中,由并发原语保护的临界区从本质上来说无非三种情况:读多写少、写多读少、读写一致。 上篇文章介绍了 sync.RWMutex并发原语,其适用于写多读少的场景;Mutex、RWMutex 其保护的都是某个临界状态区,无论是访问、存储都使用锁进行保护;当操作频繁且要求性能的情况下,锁的优化已无法满足业务需求,此时就需要考虑额外的方式来提升性能。对于读多写少的场景,最常用的方式是进行读写分离,将访问压力分散至其它组件。

Golang为了支持读多写少的场景,提供了sync.Map并发原语,由普通map、Mutex与原子变量组合而成,作为一个并发安全的map,部分情况下读、写数据通过原子操作避免加锁,从而提高临界区访问的性能,同时在高并发的情况下仍能保证数据的准确性,支持Load、Store、 Delete、 Range等操作,可以实现对sync.Map的遍历以及根据key获取value;sync.Map可以有效地替代锁的使用,提高程序的执行效率。

设计原理

讨论设计原理前,先观察下sync.Map的结构定义,主要三种元素组成:map、mutex、atomic

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Map struct {
    mu Mutex
    read atomic.Value // readOnly
    dirty map[interface{}]*entry
    misses int
}

// readOnly is an immutable struct stored atomically in the Map.read field.
type readOnly struct {
    m       map[interface{}]*entry
    amended bool
}

sync.Map 采用了装饰器 模式,在普通的map上组合其它结构进行修饰,在利用map快速定位某个元素的同时增加了读写分离的功能,当元素在只读组件read中时,使用原子操作避免加锁快速访问其数据,在sync.Map中新增元素或访问不存在的元素时操作dirty组件时使用锁操作进行保护,当只读组件read的命中率降低至一定阈值时触发状态转换,将dirty状态提升为最新的read状态,以降低访问sync.Map时加锁的次数。

下面将介绍sync.Map中每个状态字段的功能及其含义

  1. read 提供读写分离的读功能,使用atomic.Value原子操作提供并发的能力,当数据在read中时原子操作可以避免加锁提供并发访问的能力;当read的命中率降低到一定阈值时,触发将脏数据dirty更新为read,此时需要加锁保护。read实际使用map存储数据,它存储的*entry字段值可以使用CAS操作并发更新,且该*entry与dirty中存储的值指向同一地址,因此CAS修改后操作结果,read与dirty都可以观察到。
    1. Map.read的原子状态中实际存储的结构为readOnly,它是一个“只读数据”,此处的只读是指readOnly.m的值不会修改,而不是说它存储的key的value值不会修改;因此数据库概念中理解的读写分离不同。
    2. 其中m存储k-v数据,是dirty的一个子集,注意的是:它存储的某个key的数据值与dirty中该key存储的值指向同一个地址,因此当修改read中某个key的值时,dirty中该key的值也会同步修改;
    3. amended 表示dirty中是否含有readOnly.m不存在的数据.
  2. dirty 提供读写分离的写功能,是一个非线程安全的map,用Mutex锁进行保护。包含所有key的数据,其中key可以分为两类,仅存在于dirty的新数据、read与dirty中都存在的数据。当查询某个key时,如果read中不存在,且其amended状态为true,表示dirty中存在read未包含的数据,需要再查询dirty中是否包含该key.
  3. mu 互斥锁保护 read、dirty,在两种情况下需要加锁对sync.Map的状态进行保护;1. 从dirty中读写数据时,需要使用互斥锁保护,避免并发操作导致非线程安全的map触发panic;2. 从read状态更新开始累计统计加锁操作的数量misses(即命中read状态失败的次数),如果命中失败的次数大于dirty中元素的数量时,sync.Map的命中率太低需要将dirty提升为read,提升过程中使用锁进行保护。
  4. misses 累计从read更新后进行加锁操作的数量(即read状态命中失败的次数)(例如:当查询key时,如果read中未找到,需要加锁查询dirty;或向 dirty中写入数据需要加锁保护),当misses值大于等于dirty中包含的元素数量时,将dirty提升为read,并将dirty置为nil.

功能实现

sync.Map提供了Load、Store、Delete、Range基础操作,以及LoadAndDelete、LoadOrStore复合操作,下面将介绍sync.Map如何实现这些功能、以及在读多写少情况下提供高性能的原因。

Load 读取操作

  1. 从read只读的原子状态中查找,避免加锁,如果存在,则返回数据;如果不存在,且amended为false,表示dirty未包含read中不存在的数据,直接返回.
    1. 快速路径,使用原子操作,避免加锁
  2. read中不存在且amended为true(表示dirty中含有read不存在的数据),进入慢路径,查询dirty;此时先上锁,再次查询read,防止在获取互斥锁期间 dirty提升为read;仍然不存在且amended仍为true,则查寻dirty,无论是否找到,都调用missLocked将统计值misses递增,表示使用了一次互斥锁。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    // 1. 先查询只读的原子状态
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    // 2. 不存在,且amended为true,进入慢路径
    if !ok && read.amended {
        // 2.1 获取互斥锁
        m.mu.Lock()
        // 2.2 再次查询只读的原子状态,存在获取互斥锁期间read状态被更新的情况
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]
        // 2.3 仍然不存在,查询dirty
        if !ok && read.amended {
            e, ok = m.dirty[key]
            // 2.4 将统计数据累加,达到条件时,将dirty提升为read
            m.missLocked()
        }
        m.mu.Unlock()
    }
    if !ok {
        return nil, false
    }
    // 返回存储的数据
    return e.load()
}

missLocked 有两个功能:

  1. 累计read更新后命中丢失的次数
  2. 当命中丢失次数达到阈值时,将dirty提升为read,并将dirty置为nil,misses状态清0;将dirty提升read后,提升下次只读状态read查询的命中率,避免加锁。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
}

entry.load: sync.Map的 read、dirty中存储数据时,实际用户的数据是存储在entry.p中,存储了用户对象的地址,当从read或dirty查询到对象时,需要使用load 方法加载出用户对象。

如果entry.p为nil或expunged,则表示用户对象已被删除。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type entry struct {
     p unsafe.Pointer // *interface{}
}
func (e *entry) load() (value interface{}, ok bool) {
     p := atomic.LoadPointer(&e.p)

     if p == nil || p == expunged {
        return nil, false
     }
     return *(*interface{})(p), true
}

entry.p 存储值逻辑上有3种状态,value(存储了用户对象)、nil、expunged.

  1. value: 存储了用户对象,允许从一个old_value更新到new_value
  2. nil: 当删除对应的key时,不会立即将key从sync.Map中删除,而是将它的值设为nil,表示逻辑上删除;当map运行过程中,从只读的read状态构建写状态dirty时,对于值为nil的key,将其设置为expunged态,对于expunged态的key不写入dirty,当下次dirty提升为read状态时,expunged态的key由于未被任何状态记录从而被统一删除。
  3. expunged: 将一个key标识为擦除,处于该状态的key只存在于read状态中,dirty中不存在;因此当存储一个key对应的值时,如果它key对应的状态为擦除态,需要先将其修改为nil添加到dirty中,才可以更新entry对应的p值;如果直接由expunged更新为new_value,会导致read与dirty状态不一致。
    1. 一个key的删除,从逻辑上立即需要两轮 dirty->read的提升才会被真正删除;在删除key后的第一次dirty->read提升,将key的值从nil -> expunged;在第二轮将标记为expunged值丢弃由GC回收.

Store 存储操作

将一个新对象记录到sync.Map或更新已有的对象,对于不同的对象,使用不同的方式提供执行效率。

  1. 更新一个存在于read状态中的非擦除对象时,使用CAS原子操作避免加锁,提高执行效率。
  2. 更新一个存在的擦除对象时,需要加锁将对象设置为nil,添加到dirty中,再从nil更新为新值。
  3. 更新一个存在于dirty中的对象时,加锁将它的值设置为new_value
  4. 更新一个新对象时,加锁更新read的状态amended为true,表示dirty含有read不存在的对象,并将新key对应的对象存入dirty

Note: 在存储对象时,虽然某些情况下存在加锁行为,但并未累计加锁数量 misses.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (m *Map) Store(key, value interface{}) {
     // 1. 更新已存在的非擦除态对象
     // 使用CAS原子操作,避免加锁
     read, _ := m.read.Load().(readOnly)
     if e, ok := read.m[key]; ok && e.tryStore(&value) {
        return
     }

     // 2. 加锁
     m.mu.Lock()
     read, _ = m.read.Load().(readOnly)
     if e, ok := read.m[key]; ok {
         // 2.1.1 更新已存在的擦除态对象,先更新它的状态为nil,
         // 并将它添加到dirty中
         if e.unexpungeLocked() {
            // The entry was previously expunged, which implies that there is a
            // non-nil dirty map and this entry is not in it.
            m.dirty[key] = e
         }
         // 2.1.2 将nil更新为为new_value
         e.storeLocked(&value)
    } else if e, ok := m.dirty[key]; ok {
         // 2.2 更新存在与dirty中对象,将它的值更新为new_value
         e.storeLocked(&value)
    } else {
         // 2.3.1 添加一个新对象,先更新read.amended,表示dirty中存在
         // read不含有得key
         if !read.amended {
              m.dirtyLocked()
              m.read.Store(readOnly{m: read.m, amended: true})
         }
         // 2.3.2 将key对应的值存入dirty
         m.dirty[key] = newEntry(value)
     }
     m.mu.Unlock()
}

// 将entry中元素的状态由expunged修改为nil
func (e *entry) unexpungeLocked() (wasExpunged bool) {
     return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}

func (m *Map) dirtyLocked() {
     if m.dirty != nil {
        return
     }

      // 当dirty提升为read后,dirty会被置为nil
      // 因此当未来向dirty中添加元素时,需要先构建新dirty的状态
      // 因为dirty要包含所有有效的key元素,所以新构建dirty状态时,
      // 将read中存储有效值的数据也存储至dirty中
      read, _ := m.read.Load().(readOnly)
      m.dirty = make(map[interface{}]*entry, len(read.m))
      for k, e := range read.m {
           // 将包含有效值的元素存入dirty
           if !e.tryExpungeLocked() {
               m.dirty[k] = e
           }
      }
}

// 将entry中状态为nil的元素置为expunged,表示它已被逻辑删除
// 当返回值为true时,表示元素已被删除,否则,元素为一个有效值
func (e *entry) tryExpungeLocked() (isExpunged bool) {
      p := atomic.LoadPointer(&e.p)
      for p == nil {
          if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
              return true
          }
          p = atomic.LoadPointer(&e.p)
      }
      return p == expunged
}

Delete 删除操作

删除Key对应的元素,对于不同的对象,有不同的删除方式

  1. 存在于read中的对象,使用原子操作避免加锁,将查询到的对象使用CAS操作将元素值置为nil,从逻辑上删除
  2. 仅存在于dirty的对象,累计命中率丢失的次数,并使用CAS操作将元素值置为nil,从逻辑上删除
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
     m.LoadAndDelete(key)
}

func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) {
      //1. 使用原子操作查询read状态
      read, _ := m.read.Load().(readOnly)
      e, ok := read.m[key]
      if !ok && read.amended {
           //2. 未查到,加锁查询dirty
           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)
                //3. 累计加锁的数量,触发状态提升
                m.missLocked()
           }
           m.mu.Unlock()
      }
      // 4. 查询到key,将key的值置为nil,进行逻辑上删除
     if ok {
          return e.delete()
     }
     return nil, false
}

// 将元素从逻辑上进行删除,将状态置为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
         }
      }
}

Range 遍历操作

提供了O(N)的方式遍历sync.Map,用户传入遍历key-value的动作函数,如果函数返回false,则遍历终止;即使在遍历过程中发生key的并发读写操作,每个key也仅会被最多遍历一次。 因为 dirty可能包含read中不存在的key的状态,当遍历操作发生时,如果dirty与read存储的有效key的状态不一致,将dirty提升为read。

  1. read包含所有有效元素时,直接便利read中存储的值
  2. dirty含有read中不存在的元素时,将dirty提升为read,再遍历新read中存储的值
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (m *Map) Range(f func(key, value interface{}) bool) {
      read, _ := m.read.Load().(readOnly)
      // 1. dirty中包含read不存在的状态,
      // 将dirty提升为read.
      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()
       }

       // 2. 遍历read,当动作函数返回false时,退出遍历。
       // 由于遍历过程中可能存在map的并发修改操作,因此当遍历该entry时,
       // 实际存储的值被删除时,则不再遍历.
       for k, e := range read.m {
           v, ok := e.load()
           if !ok {
               continue
           }
           if !f(k, v) {
                 break
           }
       }   
}

func (e *entry) load() (value interface{}, ok bool) {
      p := atomic.LoadPointer(&e.p)
      if p == nil || p == expunged {
          return nil, false
      }
      return *(*interface{})(p), true
}

LoadAndDelete 复合操作

与Delete操作一样,仅增加了返回值传递被删除的对象.

LoadOrStore 复合操作

如果key存在,则返回已存在的值,否则将传入的参数存入Map并返回。loaded 为true ,表示返回的值为以前存在的旧值,值为false,返回的值为传入的参数。

执行逻辑与Store类似,也是4种场景

  1. read中存在key的有效值,不更新返回已存在的值
  2. read中存在key但是它的值被逻辑删除nil,则将其更新为传入的新值;
  3. read中存在key但是它的值被擦除expunged,则先将它的状态更新为nil,将它存入dirty,并更新它为传入的新值
  4. dirty中存在key的有效值,不更新且返回已存在的值
  5. dirty中存在key但是它的值被逻辑删除nil,则将其更新为传入的新值
  6. 一个新加入的key,将它存入dirty,并更新readOnly的amended状态未true
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
      // Avoid locking if it's a clean hit.
      read, _ := m.read.Load().(readOnly)
      if e, ok := read.m[key]; ok {
            actual, loaded, ok := e.tryLoadOrStore(value)
            if ok {
                return actual, loaded
            }
      }

      m.mu.Lock()
      read, _ = m.read.Load().(readOnly)
      if e, ok := read.m[key]; ok {
          if e.unexpungeLocked() {
              m.dirty[key] = e
          }
          actual, loaded, _ = e.tryLoadOrStore(value)
      } else if e, ok := m.dirty[key]; ok {
            actual, loaded, _ = e.tryLoadOrStore(value)
            m.missLocked()
      } else {
            if !read.amended {
                // We're adding the first new key to the dirty map.
                // Make sure it is allocated and mark the read-only map as incomplete.
                m.dirtyLocked()
                m.read.Store(readOnly{m: read.m, amended: true})
             }
             m.dirty[key] = newEntry(value)
             actual, loaded = value, false
      }
      m.mu.Unlock()

      return actual, loaded
}

tryLoadOrStore执行逻辑,entry中存储的值存在三种状态:有效值、nil、expunged

  1. expunged:该值已被擦除,当前仅存在于read状态中,不允许直接更新为新值,因此直接返回。
  2. 有效值:存在一个有效值,直接返回该旧值,不更新为新值。
  3. nil: key对应的对象被逻辑删除,可以被设置为新值,如果设置成功则返回。因为entry会被其它go程并发读写调用,因此更新失败时需要判断它的状态是否为expunged或有效值状态,是则表示值被其它go程更新,返回对应的值。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (e *entry) tryLoadOrStore(i interface{}) (actual interface{}, loaded, ok bool) {
      p := atomic.LoadPointer(&e.p)
      if p == expunged {
           return nil, false, false
      }
      if p != nil {
           return *(*interface{})(p), true, true
      }

      ic := i
      for {
            if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) {
                  return i, false, true
            }
            p = atomic.LoadPointer(&e.p)
            if p == expunged {
                 return nil, false, false
            }
            if p != nil {
                   return *(*interface{})(p), true, true
            }
      }
}

总结

  1. Map适用的场景:
    1. 读多写少的场景,具体到key的细节场景为下列两种情况
      1. key被一写多读的场景,因为当key存在与read中时,利用原子操作来避免上锁来提升效率;同时通过及时将dirty提升为read,减少查询读状态时的miss次数
      2. 并发更新已存在的不同key的场景,利用原子的CAS操作更新已存在的值
  2. Map不适用的场景:
    1. 读写相等或写多读少的场景,原因
      1. 因为新增的key初始仅存在于dirty中,此时的存储操作需要加锁
      2. 读写相等的情况下(如key写一次、读一次),读操作时无法从read状态命中,从而需要加锁读取dirty状态,相比于简单的Mutex增加了额外的执行逻辑;写多读少也是类似,dirty的元素增加速率大于read的命中率缺失,从而迟迟无法触发状态提升(dirty->read),会导致读取操作极容易触发加锁读取dirty的过程
    2. 大量读取不存在的key的场景,原因
      1. 频繁触发状态提升 dirty->read,因为key不存在导致read的丢失命中率极容易达到阈值
      2. Load相比于普通的Mutex加锁处理,多执行很多逻辑
  3. Map的设计原理
    1. 读写分离,使用原子操作提升并发场景下的读操作性能
    2. 采用逻辑删除,批量删除已被擦除的key,将实际的删除成本摊销
    3. 累计只读read状态的命中率缺失次数,及时将dirty提升为read,提高命中率
    4. 读写两个状态中存储对象的指针,read中数据被CAS修改后的值对两个状态都可见

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
深入Go:sync.Map
我们在使用Go的项目中需要有并发读写的map时,我们了解到Go提供sync.Map这一数据结构;通过对其简单了解,发现它正好适合我们需要的场景。随着了解的深入,我们又有了疑惑:为什么不像Java SE 8之前的ConcurrentHashMap一样,使用分段锁?为什么在内部需要一个哨兵指针expunged?这两个问题我们简单Google后都没有找到解析和讨论,因此我们决定深入sync.Map的源代码,尝试回答这两个问题。
wenxing
2021/12/14
1.5K0
深度解密Go语言之sync.map
工作中,经常会碰到并发读写 map 而造成 panic 的情况,为什么在并发读写的时候,会 panic 呢?因为在并发读写的情况下,map 里的数据会被写乱,之后就是 Garbage in, garbage out,还不如直接 panic 了。
梦醒人间
2020/05/18
2.1K0
深度解密Go语言之sync.map
深入理解Go语音的sync.Map
Go语言的并发编程是其最核心的特性之一。Go的并发模型通过goroutine和channels让并发编程变得简单而高效。然而,在并发环境下共享数据仍然是一个挑战,尤其是当涉及到共享状态的同步时。
windealli
2024/02/28
5100
深入理解Go语音的sync.Map
手摸手Go 深入浅出sync.Map
日常开发过程中,map结构应该登场率是较为频繁的。但是Go的内建map类型并不是协程安全的。如下面这个栗子,如果业务开发过程中不注意很容易中招。
用户3904122
2022/06/29
3390
手摸手Go 深入浅出sync.Map
大白话讲讲 Go 语言的 sync.Map(二)
上一篇文章 《大白话讲讲 Go 语言的 sync.Map(一)》 讲到 entry 数据结构,原因是 Go 语言标准库的 map 不是线程安全的,通过加一层抽象回避这个问题。
仁扬
2023/07/20
4020
Go 1.9 sync.Map揭秘
目录 [−] 有并发问题的map Go 1.9之前的解决方案 sync.Map Load Store Delete Range sync.Map的性能 其它 在Go 1.6之前, 内置的map类型是部分goroutine安全的,并发的读没有问题,并发的写可能有问题。自go 1.6之后, 并发地读写map会报错,这在一些知名的开源库中都存在这个问题,所以go 1.9之前的解决方案是额外绑定一个锁,封装成一个新的struct或者单独使用锁都可以。 本文带你深入到sync.Map的具体实现中,看看为了增加一个功能
李海彬
2018/03/27
9380
go 中没怎么用过的 sync.Map
我们知道 golang 的 map 并发会有问题,所以 go 官方在 sync 包中加入了一个 sync.map 来作为一个官方的并发安全的 map 实现。
LinkinStar
2022/09/01
6490
golang源码分析(30)sync.Map
    在日常开发中, 上述这种数据结构肯定不少见,因为golang的原生map是非并发安全的,所以为了保证map的并发安全,最简单的方式就是给map加锁。     之前使用过两个本地内存缓存的开源库, gcache, cache2go,其中存储缓存对象的结构都是这样,对于轻量级的缓存库,为了设计简洁(包含清理过期对象等 ) 再加上当需要缓存大量数据时有redis,memcache等明星项目解决。但是如果抛开这些因素遇到真正数量巨大的数据量时,直接对一个map加锁,当map中的值越来越多,访问map的请求越来越多,大家都竞争这一把锁显得并发访问控制变重。在go1.9引入sync.Map 之前,比较流行的做法就是使用分段锁,顾名思义就是将锁分段,将锁的粒度变小,将存储的对象分散到各个分片中,每个分片由一把锁控制,这样使得当需要对在A分片上的数据进行读写时不会影响B分片的读写。
golangLeetcode
2022/08/02
3820
golang源码分析(30)sync.Map
不得不知道的Golang之sync.Map解读!
导语 | 本文结合源码,分析sync.Map的实现思路和原理,希望为更多感兴趣的开发者提供一些经验和帮助。 一、背景 项目中遇到了需要使用高并发的map的场景,众所周知golang官方的原生map是不支持并发读写的,直接并发的读写很容易触发panic。 解决的办法有两个: 自己配一把锁(sync.Mutex),或者更加考究一点配一把读写锁(sync.RWMutex)。这种方案简约直接,但是缺点也明显,就是性能不会太高。 使用Go语言在2017年发布的Go 1.9中正式加入了并发安全的字典类型sync.M
腾讯云开发者
2022/06/14
1.8K0
不得不知道的Golang之sync.Map解读!
源码解读 sync.Map 实现原理
Go 的内建 map 是不支持并发写操作的,原因是 map 写操作不是并发安全的,当你尝试多个 Goroutine 操作同一个 map,会产生报错:fatal error: concurrent map writes。
张凯强
2020/03/25
1K1
浅谈Golang两种线程安全的map
在golang中原生map 在并发场景下,同时读写是线程不安全的,无论key是否一样。以下是测试代码
小梁编程汇
2021/12/08
3.4K0
浅谈Golang两种线程安全的map
Go 并发实战 -- sync Map
Java中线程安全的map主要有HashTable、ConcurrentHashMap,go中线程安全的Map就是sync.Map。在单协程访问时我们使用map就可以了,但是在多个协程并发访问时要使用协程安全的sync.Map,原生的map会报错。 常见的并发安全的map实现思路有HashTable那种直接锁死函数,性能比较差,或者说老ConcurrentHashMap那样分段加锁,亦或者像是像是新concurrenthashmap那种cas操作。 实际上sync.Map这三种思路都没选(因为多核情况下cache contention,reflect.New、sync.RWMutex都比较慢),sync.Map追求更好的性能和稳定性,实现思路主要面向多读少写的情况,所以写性能其实比较一般。
邹志全
2019/07/31
6150
【Golang语言社区】源码篇--sync包map
早晨看到知乎上一篇介绍Go1.9X版本部分功能,特产关注了一下;把源码想给大家呈现下,实际测试请看下一篇文章:Go语言sync.map 实际测试 package sync import ( "sync/atomic" "unsafe" ) // Map is a concurrent map with amortized-constant-time loads, stores, and deletes. // It is safe for multiple
李海彬
2018/03/27
8570
go: 当我们在使用sync.Map时,发生了什么
sync.Map是我比较喜欢的一个库,用了非常久,今天突发奇想瞧瞧它的实现。又一次被宇宙中第二NB的语言--go 折服了。 这里准备写一篇文章,讨论下当使用sync.Map执行操作的时候,会发生什么。
超级大猪
2020/07/06
1K0
[go源码] sync/map.go 源码
commit c62099cfac6b0fd46efbdab7205bb17597096472
Librant
2025/05/15
670
真希望你也明白runtime.Map和sync.Map
One of the most useful data structures in computer science is the hash table. Many hash table implementations exist with varying properties, but in general they offer fast lookups, adds, and deletes. Go provides a built-in map type that implements a hash table.
面向加薪学习
2022/12/13
3950
真希望你也明白runtime.Map和sync.Map
如何设计并实现一个线程安全的 Map ?(下篇)
在上篇中,我们已经讨论过如何去实现一个 Map 了,并且也讨论了诸多优化点。在下篇中,我们将继续讨论如何实现一个线程安全的 Map。说到线程安全,需要从概念开始说起。
一缕殇流化隐半边冰霜
2018/08/30
2.2K0
如何设计并实现一个线程安全的 Map ?(下篇)
看过这篇剖析,你还不懂 Go sync.Map 吗?
本篇文章会从使用方式和源码角度剖析 sync.Map。不过不管是日常开发还是开源项目中,好像 sync.Map 并没有得到很好的利用,大家还是习惯使用 Mutex + Map 来使用。
haohongfan
2021/04/26
8090
看过这篇剖析,你还不懂 Go sync.Map 吗?
golang 源码分析(32)sync.map
这里要重点关注readOnly.amended、Map.misses和entry.p的数值状态, 拓扑图中,多处用于走势判断. 接下来详细列出结构体的代码和注释, 方便阅读理解拓扑图.
golangLeetcode
2022/08/02
2700
golang 源码分析(32)sync.map
go map 原理与并发安全map
go map 整体和 java hashmap 差不多, 只是源码阅读的位置不太方便
leobhao
2024/11/29
1260
go map 原理与并发安全map
相关推荐
深入Go:sync.Map
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验