Go语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。

问题还原

下面来看下并发情况下读写 map 时会出现的问题

func main() {
    m := make(map[int]int)
    go func() {
        // 不停地对map进行写入
        for {
            m[1] = 1
        }
    }()

    go func() {
        // 不停地对map进行读取
        for {
            _ = m[1]
        }
    }()
}

会报错

fatal error: concurrent map read and map write

实现方案1 - 加锁

// 加锁的map
type Map struct {
    m map[int]int
    sync.RWMutex
}

func (this *Map) Get(key int) int {
    this.RLock()
    defer this.RUnlock()

    v := this.m[key]
    return v
}

func (this *Map) Set(k, v int) {
    this.Lock()
    defer this.Unlock()

    this.m[k] = v
}

func main() {
    newMap := &Map{m: make(map[int]int)}
    for i := 0; i < 1000; i++ {
        go newMap.Set(i, i)
    }
    for i := 0; i < 1000; i++ {
        go fmt.Println(i, newMap.Get(i))
    }

    time.Sleep(time.Second)
}

实现方案2 - sync.Map

type Map struct
func (m *Map) Store(key, value interface{})
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
func (m *Map) Range(f func(key, value interface{}) bool)
func (m *Map) Delete(key interface{})

sync.Map 有以下特性:

  • 无须初始化,直接声明即可。
  • sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
  • 使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
  • LoadOrStore:参数是一对key:value,如果该key存在且没有被标记删除则返回原先的value(不更新)和true;不存在则store,返回该value 和false
func main() {
    var m sync.Map
    m.Store("bb", 22)
    m.Store("cc", 33)
    m.Store("aa", 11)
    m.Store("dd", 33)
    m.Store("ee", 11)

    //Load 方法,获得value
    if v, ok := m.Load("cc"); ok {
        fmt.Printf("Load 方法,获得value   %v: %v\n", v, ok)
    }
    m.Delete("cc")

    //LoadOrStore方法,获取或者保存
    //就是如果key还在,那么就保持原来并返回原来的值,如果key不存在就存储
    if vv, ok := m.LoadOrStore("bb", 22); ok {
        fmt.Println(vv)
    } else {
        fmt.Printf("LoadOrStore 方法,获得value   %v: %v\n", vv, ok)
    }

    //遍历该map
    m.Range(func(key, value interface{}) bool {
        fmt.Printf("[%v]=[%v]\n", key, value)
        return true
    })
}

实现方案3 - 分段锁

未完,待续。。。

参考