golang之map并发访问

xiaoxiao2022-06-11  32

golang中的map不是并发安全的,并发对map读写可能会有问题,如:

// N太小时不会(比如10),因机器而异 // fatal error: concurrent map read and map write func mapDemo1() { m := make(map[string]int) go func() { for i := 0; i < N; i++ { m[strconv.Itoa(i)] = i // write } }() go func() { for i := 0; i < N; i++ { fmt.Println(i, m[strconv.Itoa(i)]) // read } }() time.Sleep(time.Second * 5) }

上面的代码, 开了两个goroutine,一个对map写,一个对map读。 在N较小时(应该要因机器而异,不是太明确,测试时N=10不会panic),在N较大时(比如1000),就会报错:

fatal error: concurrent map read and map write … 意思就是map并发读写是有问题的,会panic掉。

那么怎么办呢? 在go1.9以前,可以加锁实现:

type Cmap struct { m map[string]int //lock *sync.Mutex lock *sync.RWMutex } func (c *Cmap) Get(key string) int { c.lock.RLock() defer c.lock.RUnlock() return c.m[key] } func (c *Cmap) Set(key string, val int) { c.lock.Lock() defer c.lock.Unlock() c.m[key] = val }

这样的话,Cmap就是自身带有锁的map,在读和写的位置加锁就可以:

func mapDemo2() { m := make(map[string]int) //lock := new(sync.Mutex) lock := new(sync.RWMutex) cm := Cmap{ m: m, lock: lock, } go func() { for i := 0; i < N; i++ { cm.Set(strconv.Itoa(i), i) } }() go func() { for i := 0; i < N; i++ { fmt.Println(i, cm.Get(strconv.Itoa(i))) } }() time.Sleep(time.Second * 5) }

注意上述的两句注释,你可以用sync.Mutex (互斥锁),或者sync.RWMutex(读写锁, 支持多个读锁),对上述代码实际运行结果不影响。

不过在go版本1.9开始,有了一个sync.Map的东西,对并发做了支持。将上面的例子用sync.Map改造如下:

func mapDemo3() { var m sync.Map go func() { for i := 0; i < N; i++ { m.Store(strconv.Itoa(i), i) // 写 } }() go func() { for i := 0; i < N; i++ { v, _ := m.Load(strconv.Itoa(i)) // 读 fmt.Println(i, v) } }() time.Sleep(time.Second * 5) }

可以看到Store就是写,Load就是读。 还有几个方法,说明如下:

删除 func (m *Map) Delete(key interface{})

遍历,类似于js的forEach循环 func (m *Map) Range(f func(key, value interface{}) bool)

存或者取 func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) 这个方法有点绕,举例说下:

var m sync.Map actual, loaded := m.LoadOrStore("k1", "v1") fmt.Println(actual, loaded) // v1 false actual, loaded = m.LoadOrStore("k1", "v1") fmt.Println(actual, loaded) // v1 true actual, loaded = m.LoadOrStore("k1", "v2") fmt.Println(actual, loaded) // // v1 true actual, loaded = m.LoadOrStore("k2", "v2") fmt.Println(actual, loaded) // v2 false

仔细看完上面的例子,就会理解,这个LoadOrStore方法其实是先取, 如果有key的话就返回,没有再存储,其实就是优先取,没有再存储; 和缓存的做法很类似:先从缓存中取,没有就从数据库中读,读回来存入缓存,下次就可以从缓存中取到了。

参考: https://blog.csdn.net/skh2015java/article/details/60334091

欢迎补充指正!

转载请注明原文地址: https://www.6miu.com/read-4930669.html

最新回复(0)