golang之map并发访问

map 不是并发安全的数据结构,倘若存在并发读写行为,会抛出 fatal error.

具体规则是:

(1)并发读没有问题;

(2)并发读写中的"写"是广义上的,包含写入、更新、删除等操作;

(3)读的时候发现其他 goroutine 在并发写,抛出fatal("concurrent map read and map write")

(4)写的时候发现其他 goroutine 在并发写,抛出fatal("concurrent map writes")

需要关注,此处并发读写会引发 fatal error,是一种比 panic 更严重的错误,无法使用 recover 操作捕获.

recover哪些情况下阻止不了程序崩溃?

来自 Golang map 实现原理


map不是并发安全的!

并发安全也称线程安全,如果在并发中出现了数据的丢失或错乱,就是并发不安全

golang的map不是并发安全的,并发对map读写可能会报fatal error

复制代码
package main

import (
 "fmt"
 "time"
)

const N = 5

func main() {

 m := make(map[int]int)

 go func() {
  for i := 0; i < N; i++ {
   m[i] = i*10 + 6
  }
 }()

 go func() {
  for i := 0; i < N; i++ {
   fmt.Println(i, m[i])
  }
 }()

 time.Sleep(1e9 * 5)

}

输出为:

复制代码
0 6
1 16
2 26
3 36
4 46

上面第一个协程对map写,第二个对map读,

注:如上代码中,调换两个协程的顺序,并不会对结果有影响

当 N 较大如等于1000时,该程序将报错:


复制代码
fatal error: concurrent map read and map write

goroutine 18 [running]:
runtime.throw(0x10cb62d, 0x21)
 /usr/local/go/src/runtime/panic.go:617 +0x72 fp=0xc00002ef30 sp=0xc00002ef00 pc=0x10281e2
runtime.mapaccess1_fast64(0x10ab340, 0xc000084000, 0x0, 0x0)
 /usr/local/go/src/runtime/map_fast64.go:21 +0x1b3 fp=0xc00002ef58 sp=0xc00002ef30 pc=0x100eac3
main.main.func2(0xc000084000)
 /Users/shuangcui/go/note/map/1.go:22 +0x67 fp=0xc00002efd8 sp=0xc00002ef58 pc=0x1093c37
runtime.goexit()
 /usr/local/go/src/runtime/asm_amd64.s:1337 +0x1 fp=0xc00002efe0 sp=0xc00002efd8 pc=0x1051b51
created by main.main
 /Users/shuangcui/go/note/map/1.go:20 +0x6a

goroutine 1 [sleep]:
runtime.goparkunlock(...)
 /usr/local/go/src/runtime/proc.go:307
time.Sleep(0x12a05f200)
 /usr/local/go/src/runtime/time.go:105 +0x159
main.main()
 /Users/shuangcui/go/note/map/1.go:26 +0x7d

goroutine 17 [runnable]:
main.main.func1(0xc000084000)
 /Users/shuangcui/go/note/map/1.go:16 +0x45
created by main.main
 /Users/shuangcui/go/note/map/1.go:14 +0x48
exit status 2

解决方案:

加锁实现

可以用sync.Mutex (互斥锁),或者sync.RWMutex(读写锁)来"重构"map,

即定义一个新的结构体,为该结构体实现两个方法,在方法里使用sync包的Mutex或RWMutex来做加锁保证并发安全.

复制代码
package main

import (
 "fmt"
 "time"
 "sync"
)

const N = 1000

type Cmap struct {
 m    map[int]int
 lock *sync.RWMutex
}

func (c *Cmap) Get(key int) int {
 c.lock.RLock()
 defer c.lock.RUnlock()
 return c.m[key]
}

func (c *Cmap) Set(key, val int) {
 c.lock.Lock()
 defer c.lock.Unlock()
 c.m[key] = val*10 + 8
}

func main() {

 m := make(map[int]int)

 lock := new(sync.RWMutex)

 cm := Cmap{
  m:    m,
  lock: lock,
 }

 go func() {
  for i := 0; i < N; i++ {
   fmt.Println(i, cm.Get(i))
  }
 }()

 go func() {
  for i := 0; i < N; i++ {
   cm.Set(i, i)
  }
 }()

 time.Sleep(1e9 * 5)

}

直接使用sync.Map

从 go1.9 开始,新增了sync.Map,对并发情况下map的安全做了支持,可以对上面代码进行简化:

复制代码
package main

import (
 "sync"
 "fmt"
 "time"
)

const N = 1000

func main() {
 var m sync.Map

 go func() {
  for i := 0; i < N; i++ {
   v, _ := m.Load(i) //读
   fmt.Println(i, v)
  }
 }()

 go func() {
  for i := 0; i < N; i++ {
   m.Store(i, i*10+4) //写
  }
 }()

 time.Sleep(5 * 1e9)

}

Store为写,Load为读

除去Store和Load两个方法,sync.Map还有几个方法:

  • 删除

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) 这个方法有点绕,举例说下:

复制代码
package main

import (
 "fmt"
 "sync"
)

func main() {

 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的话就返回,没有再存储,即优先取,没有再存储;

该方法有两个返回值,第一个是键值,第二个是之前有无该键名(bool类型)

(和缓存的做法很类似:先从缓存中取,如果没有则从数据库中读,读回来存入缓存,下次就可以从缓存中取到了。)

本文由mdnice多平台发布

相关推荐
烈风逍遥24 分钟前
第七篇:提示词模板管理与 Agent 提示词编排
前端·人工智能·后端
站大爷IP35 分钟前
Python的Django ORM把我坑惨了,原来select_related和prefetch_related的区别这么大
后端
用户EasyAdminBlazor42 分钟前
AdminTable 源码解析:EasyAdminBlazor 如何实现通用 CRUD?
后端
知守观1 小时前
Spring AOP + 自定义注解实现三角色权限控制:从设计到落地的完整方案
后端
Mikko71 小时前
jackson-databind 升到 2.21.6 就安全了吗?jackson-core 是另一个坐标,它那条 high 全局库至今没收
java·后端·安全·json
Ticnix1 小时前
我调了三个月 overlap=50,它其实一次都没生效
后端·python·agent
斯维赤1 小时前
LangChain4j 入门教学(Java 后端狂喜版
java·后端
数据库小学妹1 小时前
MySQL库存扣减实战:原子UPDATE、分桶方案与锁范围分析
数据库·后端·mysql
旺仔不是程序员1 小时前
LIMIT 1:PostgreSQL 只取一行的高效查询姿势
数据库·后端·sql
知守观1 小时前
Java POI 动态二级表头导出实战:并集计算 + 合并单元格 + 冻结列的完整实现
后端