如何在Golang中实现并发Map操作_Golang sync.Map安全并发示例

Go原生map非并发安全,同时读写会panic;sync.Map专为读多写少设计,但不支持range、len,遍历需用Range方法,高频写入性能反不如加锁的普通map。

为什么不能直接用普通 map 做并发读写

Go 的原生 map 不是并发安全的——只要同时有 goroutine 在写,或者一写多读但没加锁,运行时大概率 panic:fatal error: concurrent map writesconcurrent map read and map write。这不是概率问题,是明确禁止的行为。别指望“我只读不写”就安全,因为底层哈希扩容时会触发写操作。

sync.Map 的适用场景和代价

sync.Map 是为「读多写少」场景优化的并发安全 map,内部用了读写分离 + 延迟初始化 + 普通 map + 只读快照等策略。但它不是万能替代品:

  • 不支持遍历(range)——必须用 Range() 方法传回调函数
  • 没有 len(),无法直接获取元素数量
  • 删除后 key 不会立即从内存释放(可能滞留在 dirty map 中)
  • 高频写入性能反而比加 sync.RWMutex 的普通 map 差

sync.Map 基本用法与常见错误

它提供的是方法调用接口,不是语法糖,所有操作都通过方法完成,且 key/value 类型都是 interface{},需注意类型断言风险。

var m sync.Map

// ✅ 正确:存、取、删、判断存在
m.Store("user_id_123", &User{Name: "Alice"})
if val, ok := m.Load("user_id_123"); ok {
    user := val.(*User) // 注意类型断言,失败会 panic
}
m.Delete("user_id_123")
_, exists := m.Load("user_id_123") // exists == false

// ❌ 错误:不能 range,不能用 [] 语法,不能用 len()
// for k, v := range m {} // 编译错误
// m["key"] = value        // 编译错误
// len(m)                 // 编译错误

遍历 sync.Map 的唯一安全方式

必须用 Range(),它接收一个函数参数,该函数会在每个键值对上被同步调用。注意:函数内不能修改 sync.Map(否则行为未定义),也不能依赖遍历顺序(无序)。

m.Range(func(key, value interface{}) bool {
    k := key.(string)
    u := value.(*User)
    fmt.Printf("Key: %s, User: %s\n", k, u.Name)
    return true // 继续遍历;返回 false 则中止
})

如果需要先收集全部 key 再处理,得自己建 slice 存下来——Range() 是唯一入口,没有「获取所有 key」的方法。

sync.Map 的真正难点不在用法,而在判断「我是不是真该用它」:写多?要 len?要 range?要稳定 GC 行为?这些时候老老实实用 sync.RWMutex + map 更可控。它不是并发 map 的银弹,而是特定负载下的折中解。