Skip to content
鼓励作者:欢迎打赏犒劳

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 }()

为了解决这个问题,通常有几种方式:

  1. sync.Mutexsync.RWMutex 手动加锁;
  2. 使用通道(channel)串行化访问;
  3. 使用 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)

六、注意事项

  1. 没有长度方法:无法直接获取元素个数(需自己用 Range 统计)。
  2. 类型不安全:存入 int,取出是 interface{},需类型断言:
    go
    if v, ok := sm.Load("age"); ok {
        age := v.(int) // 注意类型安全!
    }
    (Go 1.18+ 可封装泛型 wrapper 提升安全性)
  3. 性能并非总是最优:高竞争写入场景下可能不如带锁的 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。

如有转载或 CV 的请标注本站原文地址