温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何使用Go sync.Map

发布时间:2021-10-14 09:12:03 来源:亿速云 阅读:252 作者:iii 栏目:编程语言

这篇文章主要讲解了“如何使用Go sync.Map”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何使用Go sync.Map”吧!

下面这段代码,看起来很有道理,其实是用错了(背景:并发场景中获取注册信息)。

instance, ok := instanceMap[name]if ok {
    return instance, nil}

initLock.Lock()defer initLock.Unlock()// double checkinstance, ok = instanceMap[name]if ok {
    return instance, nil}

这里使用使用 sync.Map 会更合理些,因为 sync.Map 底层完全包含了这个逻辑。可能写 Java 的同学看着上面这段代码很眼熟,但确实是用错了,关于为什么用错了以及会造成什么影响,请大家关注后续的文章。

我大概分析了下大家宁愿使用 Mutex + Map,也不愿使用 sync.Map 的原因:

  1. sync.Map 本身就很难用,使用起来并不像一个 Map。失去了 map 应有的特权语法,如:make,  map[1] 等

  2. sync.Map 方法较多。让一个简单的 Map 使用起来有了较高的学习成本。

不管什么样的原因吧,当你读过这篇文章后,在某些特定的并发场景下,建议使用 sync.Map 代替 Map + Mutex 的。

用法全解

package mainimport (
 "fmt" "sync")func main() {
 var syncMap sync.Map
 syncMap.Store("11", 11)
 syncMap.Store("22", 22)

 fmt.Println(syncMap.Load("11")) // 11 fmt.Println(syncMap.Load("33")) // 空 fmt.Println(syncMap.LoadOrStore("33", 33)) // 33 fmt.Println(syncMap.Load("33")) // 33 fmt.Println(syncMap.LoadAndDelete("33")) // 33 fmt.Println(syncMap.Load("33")) // 空 syncMap.Range(func(key, value interface{}) bool {
  fmt.Printf("key:%v value:%v\n", key, value)
  return true })
    // key:22 value:22 // key:11 value:11}

其实 sync.Map 并不复杂,只是将普通 map 的相关操作转成对应函数而已。

 普通 mapsync.Map
map 获取某个 keymap[1]sync.Load(1)
map 添加元素map[1] = 10sync.Store(1, 10)
map 删除一个 keydelete(map, 1)sync.Delete(1)
遍历 mapfor...rangesync.Range()

sync.Map 两个特有的函数,不过从字面就能理解是什么意思了。LoadOrStore:sync.Map 存在就返回,不存在就插入 LoadAndDelete:sync.Map 获取某个 key,如果存在的话,同时删除这个 key

源码解析

type Map struct {
 mu Mutex
 read atomic.Value // readOnly  read map dirty map[interface{}]*entry  // dirty map misses int}

如何使用Go sync.Map

sync map 全景图

如何使用Go sync.Map

Load

如何使用Go sync.Map

Store

如何使用Go sync.Map

Delete

read map 的值是什么时间更新的 ?

  1. Load/LoadOrStore/LoadAndDelete 时,当 misses 数量大于等于 dirty map 的元素个数时,会整体复制 dirty map 到 read map

  2. Store/LoadOrStore 时,当 read map 中存在这个key,则更新

  3. Delete/LoadAndDelete 时,如果 read map 中存在这个key,则设置这个值为 nil

dirty map 的值是什么时间更新的 ?

  1. 完全是一个新 key, 第一次插入 sync.Map,必先插入 dirty map

  2. Store/LoadOrStore 时,当 read map 中不存在这个key,在 dirty map 存在这个key,则更新

  3. Delete/LoadAndDelete 时,如果 read map 中不存在这个key,在 dirty map 存在这个key,则从 dirty map 中删除这个key

  4. 当 misses 数量大于等于 dirty map 的元素个数时,会整体复制 dirty map 到 read map,同时设置 dirty map 为 nil

疑问:当 dirty map 复制到 read map 后,将 dirty map 设置为 nil,也就是 dirty map 中就不存在这个 key 了。如果又新插入某个 key,多次访问后达到了 dirty map 往 read map 复制的条件,如果直接用 read map 覆盖 dirty map,那岂不是就丢了之前在 read map 但不在 dirty map 的 key ?

答:其实并不会。当 dirty map 向 read map 复制后,readOnly.amended 等于了 false。当新插入了一个值时,会将 read map 中的值,重新给 dirty map 赋值一遍,也就是 read map 也会向 dirty map 中复制。

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

 read, _ := m.read.Load().(readOnly)
 m.dirty = make(map[interface{}]*entry, len(read.m))
 for k, e := range read.m {
  if !e.tryExpungeLocked() {
   m.dirty[k] = e
  }
 }
}

read map 和 dirty map 是什么时间删除的?

  • 当 read map 中存在某个 key 的时候,这个时候只会删除 read map, 并不会删除 dirty map(因为 dirty map 不存在这个值)

  • 当 read map 中不存在时,才会去删除 dirty map 里面的值

疑问:如果按照这个删除方式,那岂不是 dirty map 中会有残余的 key,导致没删除掉?

答:其实并不会。当 misses 数量大于等于 dirty map 的元素个数时,会整体复制 dirty map 到 read map。这个过程中还附带了另外一个操作:将 dirty map 置为 nil。

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}

read map 与 dirty map 的关系 ?

  1. 在 read map 中存在的值,在 dirty map 中可能不存在。

  2. 在 dirty map 中存在的值,在 read map 中也可能存在。

  3. 当访问多次,发现 dirty map 中存在,read map  中不存在,导致 misses 数量大于等于 dirty map 的元素个数时,会整体复制 dirty map 到 read map。

  4. 当出现 dirty map 向 read map 复制后,dirty map 会被置成 nil。

  5. 当出现 dirty map 向 read map 复制后,readOnly.amended 等于了 false。当新插入了一个值时,会将 read map 中的值,重新给 dirty map 赋值一遍

read/dirty map 中的值一定是有效的吗?

并不一定。放入到 read/dirty map 中的值总共有 3 种类型:

  • nil:如果获取到的 value 是 nil,那说明这个 key 是已经删除过的。既不在 read map,也不在 dirty map

  • expunged:这个 key 在 dirty map 中是不存在的

  • valid:其实就正常的情况,要么这个值存在在 read map 中,要么存在在 dirty map 中

sync.Map 是如何提高性能的?

通过源码解析,我们知道 sync.Map 里面有两个普通 map,read map主要是负责读,dirty map 是负责读和写(加锁)。在读多写少的场景下,read map 的值基本不发生变化,可以让 read map 做到无锁操作,就减少了使用 Mutex + Map 必须的加锁/解锁环节,因此也就提高了性能。

不过也能够看出来,read map 也是会发生变化的,如果某些 key 写操作特别频繁的话,sync.Map 基本也就退化成了 Mutex + Map(有可能性能还不如 Mutex + Map)。

所以,不是说使用了 sync.Map 就一定能提高程序性能,我们日常使用中尽量注意拆分粒度来使用 sync.Map。

关于如何分析 sync.Map 是否优化了程序性能,同样可以使用 pprof。具体过程可以参考 《这可能是最容易理解的 Go Mutex 源码剖析》

sync.Map 应用场景

  1. 读多写少

  2. 写操作也多,但是修改的 key 和读取的 key 特别不重合。

关于第二点我觉得挺扯的,毕竟我们很难把控这一点,不过由于是官方的注释还是放在这里。

实际开发中我们要注意使用场景和擅用 pprof 来分析程序性能。

sync.Map 使用注意点

和 Mutex 一样, sync.Map 也同样不能被复制,因为 atomic.Value 是不能被复制的。

感谢各位的阅读,以上就是“如何使用Go sync.Map”的内容了,经过本文的学习后,相信大家对如何使用Go sync.Map这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

go
AI