并发安全且高效的sync.Map详情 有必要了解一下

信息分类: golang 发布时间: 2022-09-27 11:18:18 访问量: 445

Go Map在并发读写场景下经常会遇到panic的情况。为什么呢?因为在并发读写的情况下,map 里的数据会被写乱,map非并发安全的。


要解决并发读写map问题,现在主要有两种解决方法:


1:一般采用map + 互斥锁 或者 读写锁的方式实现。

2:用标准库 sync.Map (Go 1.9 及之后),这是并发安全的Map。主要适用于读多写少的场景。


一、sync.Map是什么?


官方接口文档地址:https://pkg.go.dev/sync#Map


sync.map 是线性安全的,读取、插入、删除都保持常数级的时间复杂度。
sync.map 的零值是有效的,并且零值是一个空的 map。在第一次使用之后,不允许被拷贝。


二、sync.Map有什么用?


使用sync.map,多个 goroutine 的并发使用是安全的,对 map 的读写,不需要加锁。并且它通过空间换时间的方式,使用 read 和 dirty 两个 map 来进行读写分离,降低锁时间来提高效率。使用 sync.Map 类型可以大大减少锁的争夺。


sync.map 适用于读多写少的场景。对于写多的场景,会导致 read map 缓存失效,需要加锁,导致冲突变多;而且由于未命中 read map 次数过多,导致 dirty map 提升为 read map,这是一个 O(N) 的操作,会进一步降低性能。


三、sync.Map使用示例代码

package main

import (
    "fmt"
    "sync"
)

func main()  {
    var m sync.Map
    // 1. 写入
    m.Store("xiao6", 25)
    m.Store("superl", 26)

    // 2. 读取
    age, _ := m.Load("xiao6")
    fmt.Println(age.(int))

    // 3. 遍历
    m.Range(func(key, value interface{}) bool {
        name := key.(string)
        age := value.(int)
        fmt.Println(name, age)
        return true
    })

    // 4. 删除
    m.Delete("xiao6")
    age, ok := m.Load("xiao6")
    fmt.Println(age, ok)

    // 5. 读取或写入
    m.LoadOrStore("superl", 100)
    age, _ = m.Load("superl")
    fmt.Println(age)
}


第 1 步,写入两个 k-v 对;
第 2 步,使用 Load 方法读取其中的一个 key;
第 3 步,遍历所有的 k-v 对,并打印出来;
第 4 步,删除其中的一个 key,再读这个 key,得到的就是 nil;
第 5 步,使用 LoadOrStore,尝试读取或写入key,因为这个 key 已经存在,因此写入不成功,并且读出原值。


四、关于sync.Map与Map的性能对比




sync.Map的性能高体现在读操作远多于写操作的时候。 

极端情况下,只有读操作时,是普通map的性能的44.3倍。

反过来,如果是全写,没有读,那么sync.Map还不如加普通map+mutex锁呢。只有普通map性能的一半。


建议使用sync.Map时一定要考虑读定比例。当写操作只占总操作的<=1/10的时候,使用sync.Map性能会明显高很多。


如有转载,请注明出处!《并发安全且高效的sync.Map详情 有必要了解一下》的原文地址:http://www.xiao6.net/post/249.html