sync.Map.Delete不适用于高频并发删除,每秒超50次写入时性能反低于加锁普通map;其惰性删除、内存屏障及dirty map开销导致吞吐下降,应优先选用分片锁、concurrent-map或重建实例等替代方案。

sync.Map.Delete 在高并发删除场景下性能很差
别指望 Delete 能扛住每秒几百次以上的并发删除。它不是为高频写设计的,底层有原子操作、内存屏障、指针跳转和 dirty map 提升开销,写多时比加锁普通 map 还慢。
常见错误现象:fatal error: concurrent map writes 一出现,就换 sync.Map,结果压测 QPS 不升反降——因为问题本该用分片锁或 sync.Pool + 预分配解决,而不是硬套 sync.Map。
- 每秒写入超 50 次(含
Store/Delete),sync.Map的吞吐通常低于加锁的普通 map -
Delete是惰性删除:只标记,不立即释放内存;后续Load或Range才可能触发清理,带来不可预测延迟 - 大量
Delete后紧接Range,会强制提升 dirty map,引发一次性扩容成本
CompareAndDelete 是条件删除的正确姿势(Go 1.20+)
如果你要“只有值等于某预期时才删”,直接用 CompareAndDelete,别自己 Load + if + Delete —— 后者不是原子的,中间可能被其他 goroutine 修改。
示例:
m := sync.Map{}
m.Store("token:123", &user{ID: 123, Version: 5})
// ✅ 原子判断并删除:仅当当前值的 Version == 5 时才删
deleted := m.CompareAndDelete("token:123", &user{ID: 123, Version: 5})
// ❌ 错误:非原子,竞态风险
if v, ok := m.Load("token:123"); ok {
if u, ok := v.(*user); ok && u.Version == 5 {
m.Delete("token:123") // 此刻 v 可能已被覆盖
}
}
-
CompareAndDelete返回bool,失败不 panic,需显式判断 - 传入的
old值必须是同一地址或深度相等(取决于是否实现Equal方法);对结构体建议用指针比较 - 不支持 nil 比较:若旧值可能是 nil,请先
Load判断再调用
高频删除场景该用什么替代 sync.Map
真要每秒删几百 key,sync.Map 是错选。它只适合“创建一次、读一百次、删零次”的缓存类场景。
- 实时计数器、消息队列 pending 表、session 清退列表 → 用分片
map+sync.RWMutex(比如 64 个 bucket,key hash 后取模) - 短期存活、批量过期的键(如 JWT 黑名单)→ 用
sync.Map+ 定时Range扫描 +Delete,但扫描本身要控制频率,避免阻塞 - 需要精确长度、频繁增删、强一致性 → 直接上
concurrent-map(第三方库)或自研带 CAS 的分段哈希表 - 删除后立刻重用 key → 普通 map +
sync.Mutex往往更稳,锁粒度可控,无指针跳转损耗
Range 遍历中 Delete 不影响当前迭代,但会漏删新插入项
Range 是快照式遍历:它按开始时的 read + dirty 状态走一遍,期间发生的 Delete 或 Store 都不会反映在本次回调里。
这意味着:
- 你在
Range回调里调Delete(key),这个 key 本次不会从迭代中消失(它本来就在快照里),但下次Load就查不到 - 如果遍历时有 goroutine 正在
Store新 key,这些 key 不会被本次Range遍历到 -
Range无法替代原子批量删除;想清空全部,只能反复Range+Delete直到Range不再进入回调(不推荐,易饿死)
真正需要清空时,别靠 Range:新建一个 sync.Map 替换旧引用,让 GC 回收旧对象更干脆。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











