go map并发读写会触发fatal error: concurrent map read and map write,必须用sync.rwmutex或sync.map保护;sync.rwmutex适合通用场景,sync.map仅适用于读远多于写且无需遍历的场景;务必用go run -race检测竞态。

Go map并发读写panic的典型现象
程序突然崩溃,堆栈里出现 fatal error: concurrent map read and map write —— 这不是偶发错误,是Go运行时主动终止进程的保护行为。它只在**实际发生竞态时触发**,不依赖race detector,但也不报具体哪行代码出问题,只告诉你“有俩goroutine同时碰了同一个map”。
常见场景包括:全局map[string]interface{}被多个HTTP handler共用、worker池共享状态map、缓存map没加锁就直接读写。
用sync.RWMutex保护map是最稳妥的方案
别试图靠“我保证不会同时读写”来绕过,Go的调度不可控,哪怕读操作多、写极少,也大概率崩。用sync.RWMutex是标准解法,读多写少时性能足够好。
- 写操作前调用
mu.Lock(),完后mu.Unlock() - 读操作前用
mu.RLock(),完后mu.RUnlock() - 避免在锁内做耗时操作(比如HTTP调用、数据库查询),否则会阻塞其他goroutine
- 不要把
mu和map分开传参,容易漏锁;建议封装成结构体字段
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, val string) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = val
}
sync.Map适合高频读+低频写且无需遍历的场景
sync.Map是Go内置的并发安全map,但它不是万能替代品。它的设计目标很明确:读远多于写、key生命周期长、不需要遍历全部元素。
立即学习“go语言免费学习笔记(深入)”;
-
Load/Store开销比普通map高,频繁写反而更慢 - 不支持
for range遍历,只能用Range回调,且遍历期间无法保证看到所有最新写入 - 内存占用更高,内部用了分片+原子操作+懒删除,对GC压力略大
- 如果业务需要按key排序、批量删除、或统计长度,
sync.Map会让你写更多胶水代码
go run -race是唯一靠谱的竞态预检手段
光靠本地测试很难复现map竞态——它高度依赖goroutine调度时机。必须用go run -race或go test -race跑,才能在开发阶段暴露问题。
- 开启race检测后,二进制体积变大、运行变慢,**绝不能用于生产环境**
- 它能精确定位到哪两行代码在竞争,比如:
Previous write at ... by goroutine 7+Current read at ... by goroutine 8 - CI中强制加入
-race测试,能拦住90%的并发隐患 - 注意:
sync.Map本身是race-safe的,但如果你把它当成普通map裸用(比如类型断言后直接取m.(map[string]int),race detector照样会报警
sync.Mutex代替RWMutex导致读吞吐骤降,或者在defer里unlock却忘了lock成功与否。还有人把map指针传进goroutine后,在原goroutine里直接改map底层数组,这种逃逸行为race detector都抓不到。










