sync.Map.Delete 在每秒超50次并发删除时性能反低于加锁map,因其高开销写路径(检查read map、降级判断、原子置空、内存屏障)比RWMutex操作更重,且Delete为惰性删除,易引发恶性循环;高频删除应改用分片锁map、concurrent-map库或重建实例。
sync.Map.Delete 在每秒超 50 次并发删除时性能反低于加锁 map
它不是“删得轻”的操作,而是和 store 同级的高开销写路径。每次 delete 都要:检查 read map 是否命中、判断是否需降级到 dirty、做原子指针置空、触发内存屏障——这些加起来比一次 sync.rwmutex.lock() + delete + unlock() 更重。
压测中常见现象:QPS 不升反降、CPU 利用率飙升但吞吐不涨、大量 goroutine 卡在 runtime.semawakeup。这不是你代码写错了,是选型错配了。
- 每秒写入(含
Store/Delete)超过 50 次,sync.Map吞吐大概率不如加锁原生map -
Delete是惰性删除:只把entry.p置为nil,不释放内存;后续Load或Range才可能清理,带来延迟毛刺 - 高频删热点 key 会反复污染
readmap 的entry,导致LoadOrStore失效、强制写入dirty,形成恶性循环
CompareAndDelete 是唯一安全的条件删除方式(Go 1.20+)
别再手写 Load + if + Delete——这三步之间没有原子性,中间值可能被其他 goroutine 覆盖,造成误删或漏删。Go 1.20 引入的 CompareAndDelete 就是干这个的。
它要求传入的 old 值必须与当前值“深度相等”或同一地址。对结构体,强烈建议传指针;对可能为 nil 的场景,先 Load 判断再调用。
-
CompareAndDelete返回bool,失败不 panic,必须显式判断结果 - 错误写法:
if v, ok := m.Load(k); ok && v == expected { m.Delete(k) }—— 这是竞态漏洞 - 正确写法:
deleted := m.CompareAndDelete(k, &user{ID: 123, Version: 5}),前提是user实现了Equal方法或用指针比较
高频删除场景该换什么?别硬扛 sync.Map
sync.Map 的设计目标很明确:「创建一次、读一百次、删零次」。真要每秒删几百 key,它就是错选。替代方案不是理论,而是实测有效的工程选择:
立即学习“go语言免费学习笔记(深入)”;
-
分片锁 map:把大 map 拆成 32 或 64 个子 map,每个配独立
sync.Mutex,key 用哈希路由,吞吐通常提升 20–40% -
concurrent-map(第三方库):提供
Remove、Count、Keys等缺失能力,API 更贴近直觉,且内部做了分片优化 -
重建实例:若删除是批量、周期性的(如每分钟清空过期 session),直接新建
sync.Map{},旧实例交给 GC,简单有效
Range 不是遍历工具,更不是删除工具
Range 内部会先加全局锁、拷贝当前快照,然后遍历。它不能边删边遍历,也不能中断后继续——一旦开始,就得跑完或靠回调返回 false 提前退出。更关键的是:大量 Delete 后紧接 Range,会强制触发 dirty map 提升,引发一次性扩容成本,放大延迟。
最容易被忽略的一点:Delete 和 Store 共享同一套 dirty map 升级逻辑。你以为只是删,其实是在高频写。而 sync.Map 的写路径,本就不是为这个设计的。


















