在 Go 中,map 默认不是线程安全的。如果多个 goroutine 同时读写同一个 map 而没有同步,就会出现数据竞争(data race),导致恐慌 fatal error: concurrent map read and map write 或数据损坏。
为了安全地处理 map,必须:
sync.Map,该包旨在处理高负载的竞争场景。var mu sync.RWMutex m := make(map[string]int) // 写入 mu.Lock() m["key"] = 1 mu.Unlock() // 读取 mu.RLock() v := m["key"] mu.RUnlock() // 或者使用 sync.Map var sm sync.Map sm.Store("key", 1) v, _ := sm.Load("key")
为什么在 Go 中从不同的 goroutine 同时读取和写入 map 是如此危险,尽管 map 是内置类型?
答案:在 Go 中,内置类型 map 不提供同步。多个 goroutine 同时访问 map 不会产生不正确的值,而可能导致程序崩溃。即使是同时读取和写入(当没有重叠的键时!)也会导致致命错误。这与某些其他语言的集合“耐受”并发访问不同。
故事
在一个真实项目中,开发者使用全局 map 来缓存数据。服务在测试中稳定运行,但在负载测试和生产中开始崩溃,出现错误 fatal error: concurrent map read and map write。原因是在没有使用 Mutex 的情况下,不同的 http 请求并行访问 map。
故事
在 Go 的 web 应用程序中,一位程序员决定提高性能,并使用普通 map 作为连接池,认为多线程是由框架保证的。在流量突然增长时,服务开始无故崩溃:由于竞争,map 中的数据遭到破坏并出现恐慌。
故事
在一个内部 Go 服务中,应用程序使用 map 来实时收集统计数据,认为主要的访问只是写入。实际上,另一部分代码会定期请求数据以生成报告,导致每天只发生一次难以捕捉的崩溃——恰好在统计数据触发时。分析显示,读取和写入在没有任何锁定的情况下重叠。