
SyncMap的使用
sync.Map 是 Go 语言标准库 sync 包中提供的一种 并发安全的 map 类型。
一、为什么需要 sync.Map?
Go 原生的 map 不是并发安全的。如果多个 goroutine 同时对一个普通 map 进行读写(尤其是写),会导致 panic(data race):
go
// 危险!多个 goroutine 并发写 map 会 panic
var m = make(map[string]int)
go func() { m["a"] = 1 }()
go func() { m["b"] = 2 }()为了解决这个问题,通常有几种方式:
- 用
sync.Mutex或sync.RWMutex手动加锁; - 使用通道(channel)串行化访问;
- 使用
sync.Map—— Go 官方提供的并发安全 map。
二、sync.Map 的特点
| 特性 | 说明 |
|---|---|
| ✅ 并发安全 | 多个 goroutine 可以同时读写,无需额外加锁 |
⚠️ 不支持 len()、range 等语法 | 必须使用其方法操作 |
| ⚠️ 无泛型(Go 1.18 之前) | 键和值都是 interface{} 类型(Go 1.21+ 虽然支持泛型,但 sync.Map 本身仍未泛型化) |
| 🔧 适合特定场景 | 官方文档建议:仅在以下情况使用: • 读远多于写 • 多个 goroutine 访问不同的 key(即 key 集合基本不变或增长缓慢) |
📌 注意:不要盲目用
sync.Map替代所有 map!对于大多数普通场景,map + RWMutex性能更好、类型更安全。
三、基本用法
go
package main
import (
"fmt"
"sync"
)
func main() {
var sm sync.Map
// 写入
sm.Store("name", "Alice")
sm.Store("age", 30)
// 读取
if val, ok := sm.Load("name"); ok {
fmt.Println("name:", val) // name: Alice
}
// 读取或写入(不存在则写入)
actual, loaded := sm.LoadOrStore("city", "Beijing")
if loaded {
fmt.Println("已存在:", actual)
} else {
fmt.Println("新写入:", actual) // 新写入: Beijing
}
// 删除
sm.Delete("age")
// 遍历
sm.Range(func(key, value interface{}) bool {
fmt.Println(key, "=", value)
return true // 返回 false 会中断遍历
})
}四、常用方法
| 方法 | 说明 |
|---|---|
Store(key, value interface{}) | 存储键值对 |
Load(key interface{}) (value interface{}, ok bool) | 读取,ok 表示是否存在 |
LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) | 如果 key 不存在则存入并返回新值;否则返回已有值 |
LoadAndDelete(key interface{}) (value interface{}, loaded bool) | 读取并删除 |
Delete(key interface{}) | 删除键 |
Range(f func(key, value interface{}) bool) | 遍历所有键值对 |
五、适用场景举例
- 缓存(如用户 session 缓存)
- 全局配置映射(key 基本固定,频繁读)
- 统计计数器(不同 goroutine 更新不同 key)
六、注意事项
- 没有长度方法:无法直接获取元素个数(需自己用
Range统计)。 - 类型不安全:存入
int,取出是interface{},需类型断言:go(Go 1.18+ 可封装泛型 wrapper 提升安全性)if v, ok := sm.Load("age"); ok { age := v.(int) // 注意类型安全! } - 性能并非总是最优:高竞争写入场景下可能不如带锁的 map。
总结
sync.Map是 Go 提供的一个 开箱即用的并发安全 map,适用于 读多写少、key 分散 的场景。
但它不是万能的,不要把它当作普通 map 的并发替代品,应根据实际场景选择合适方案。
封装一个工具类
🔧 封装一个泛型版的 SyncMap[K, V]
下面是一个安全、易用、类型安全的泛型封装,底层使用 sync.Map:
go
package sync_map_util
import (
"sync"
)
// SyncMap 是 sync.Map 的泛型封装
type SyncMap[K comparable, V any] struct {
m sync.Map
}
// Store 设置键值对
func (sm *SyncMap[K, V]) Store(key K, value V) {
sm.m.Store(key, value)
}
// Load 获取值,第二个返回值表示 key 是否存在
func (sm *SyncMap[K, V]) Load(key K) (value V, ok bool) {
v, ok := sm.m.Load(key)
if !ok {
var zero V
return zero, false
}
return v.(V), true
}
// LoadOrStore 如果 key 不存在则存储并返回新值;否则返回已有值
func (sm *SyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
v, loaded := sm.m.LoadOrStore(key, value)
return v.(V), loaded
}
// LoadAndDelete 加载并删除
func (sm *SyncMap[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
v, loaded := sm.m.LoadAndDelete(key)
if !loaded {
var zero V
return zero, false
}
return v.(V), true
}
// Delete 删除键
func (sm *SyncMap[K, V]) Delete(key K) {
sm.m.Delete(key)
}
// Range 遍历所有元素,f 返回 false 时停止遍历
func (sm *SyncMap[K, V]) Range(f func(key K, value V) bool) {
sm.m.Range(func(key, value interface{}) bool {
return f(key.(K), value.(V))
})
}
// Len 获取元素个数(注意:非原子操作,仅用于调试或低并发场景)
func (sm *SyncMap[K, V]) Len() int {
count := 0
sm.Range(func(key K, value V) bool {
count++
return true
})
return count
}🧪 使用示例
go
package main
import "fmt"
func main() {
// 创建一个 string -> int 的并发安全 map
var m SyncMap[string, int]
m.Store("apple", 10)
m.Store("banana", 20)
if val, ok := m.Load("apple"); ok {
fmt.Println("apple =", val) // apple = 10
}
m.Range(func(k string, v int) bool {
fmt.Printf("%s: %d\n", k, v)
return true // 继续遍历
})
fmt.Println("Total items:", m.Len()) // Total items: 2
}⚠️ 注意事项
Len()不是原子操作,也不高效(需要遍历),不要在高并发或性能敏感路径中使用。- 这个封装保留了
sync.Map的所有语义和适用场景(读多写少、key 分散等)。 - 类型安全:编译期就能检查 key 和 value 的类型,避免运行时 panic。

