Go 语言为什么不在语言层面保证 map 线程安全?

普通 map 没有并发安全保证,是 Go 留下的一条设计边界,不是遗漏。大多数 map 用不上内置锁;真遇到并发更新时,程序需要保护的范围往往又比一次 map 访问更大。Go 因而把同步范围交给调用代码决定。

map 的每次访问都加上锁,下面这段代码照样可能错:

hljs 复制代码
if _, ok := users[id]; !ok {
 users[id] = user
}

假设 map 已经替每一次查询和写入加好了锁。两个 goroutine 仍然可以按这个顺序执行:

hljs 复制代码
goroutine A:查询 id,不存在
goroutine B:查询 id,也不存在
goroutine A:写入 userA
goroutine B:写入 userB

四次访问都没有撞车,两次写入却都越过了 !ok。最后留下谁取决于执行顺序,两个调用方还都会以为自己完成了"仅在不存在时插入"。

这段代码需要把查询和写入包进同一个临界区。拆成两次各自安全的操作,条件就会漏掉。内置 map 只看得见一次 users[id],它看不懂前面的 if 与后面的赋值原来是一件事,更猜不到有些操作还要连同另一个字段一起保护。

如果要给这层区别找个术语,一次操作看起来在调用与返回之间的某个瞬间完成,叫作可线性化;连续几次调用不会因此自动变成一笔事务。给 map 内置锁,最多替单次操作划线。业务上的那条线应该从哪里开始、在哪里结束,只有调用代码知道。

这不等于并发 map 做不出来,sync.Map 就在标准库里。它的 LoadOrStore 正好说明了边界应该怎样表达:key 已存在就返回旧值,不存在才存入新值。把一次 Load 和一次 Store 分开调用,得不到同样的保证。它的文档也明确说 sync.Map 是专用类型,适合某个条目只写一次、读取很多次,或者不同 goroutine 大多操作不相交的 key;多数代码仍适合普通 map 配一把单独的锁,因为这把锁还能顺手保护 map 以外的数据关系。sync.Map 文档

问题在于要不要把这份保证加到每一个普通 map 上。Go 官方 FAQ 明确提到,语言并不排斥实现原子的 map 更新;只是多数 map 根本不需要多个 goroutine 同时修改。真有这种需要时,map 往往已经属于一个更大的数据结构,而那个结构本来就要同步。若所有 map 操作都默认获取互斥锁,大多数程序会多出不需要的同步开销,组合操作仍要由外部锁保护。

普通 map 的边界也别记混。例如先完成初始化,再启动读取它的 goroutine;此后只要没人更新,查找和 range 可以并发进行。一旦赋值、删除或 clear 可能与其他访问重叠,就要用锁或 channel 把访问串起来;写入不同的 key 也不例外。Go 内存模型

上面的"仅在不存在时插入",用普通 map 可以这样收口:

hljs 复制代码
type UserStore struct {
 mu    sync.Mutex
 users map[int]*User
}

func NewUserStore() *UserStore {
 return &UserStore{users: make(map[int]*User)}
}

func (s *UserStore) PutIfAbsent(id int, user *User) bool {
 s.mu.Lock()
 defer s.mu.Unlock()

 if _, ok := s.users[id]; ok {
  return false
 }
 s.users[id] = user
 return true
}

PutIfAbsent 返回 true 时,查询和写入已经一起走完。要是只在 s.users[id] = user 那一行上锁,锁确实加了,那个 if 还是会漏人过去。

相关推荐
AlienZHOU2 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
狗头大军之江苏分军4 小时前
《潮水漫过十七岁》开学了
后端
苏三说技术4 小时前
如何看待GPT-6在UP主众测中碾压夺冠?它是现在最强大模型吗?
后端
mldong5 小时前
一份 JSON,一条能跑的审批流:把报销流程送上工作流引擎
后端·架构
wno7045 小时前
Spring Boot WebFlux增删改查
java·spring boot·后端
Captaincc5 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
aramae6 小时前
模拟实现strlen()函数 (C语言)
c语言·开发语言·后端
IT_陈寒7 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
分支预测失败7 小时前
RISC-V 时间子系统深度专题:mtime 访问路径、SBI 定时器与 Linux tickless 协同
后端
65岁退休Coder7 小时前
PI Agent 开发一个生产级 Harness
后端·node.js·agent