在并发编程中,共享资源的访问是一个绕不开的问题。Go 中的
map
在并发读写时是不安全的,直接使用可能导致程序 panic。因此,在多协程同时访问 Map 的场景下,必须采取有效的同步措施。
本篇将通过一个实战案例,介绍 Go 的并发神器 ------ sync.Map
,并展示其使用方式和应用场景。
一、为什么普通 map
不安全?
go
m := make(map[string]int)
go func() {
m["a"] = 1
}()
go func() {
fmt.Println(m["a"])
}()
上述代码在运行时,极有可能 panic:
arduino
fatal error: concurrent map read and map write
这是因为 Go 的原生 map
类型并不支持并发读写。
二、解决方式有哪些?
- 使用
sync.Mutex
+ map 手动加锁; - 使用
sync.RWMutex
读写分离锁; - 使用 Go 官方提供的
sync.Map
------ 最简便的线程安全 map 实现!
三、什么是 sync.Map
?
sync.Map
是 Go 标准库提供的并发安全 map,特点包括:
- 内置锁机制,适合高并发读写;
- 无需手动加锁;
- 内部采用读优化结构(读多写少场景性能更佳);
- 提供一组特定 API(不能用下标访问,如
m["a"]
)。
四、实战案例:使用 sync.Map
构建并发计数器
我们实现一个并发访问的计数器,多个 goroutine 同时对多个 key 进行访问和计数。
示例代码:
go
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var counter sync.Map
var wg sync.WaitGroup
keys := []string{"A", "B", "C"}
// 每个 key 启动 10 个协程并发计数
for _, key := range keys {
for i := 0; i < 10; i++ {
wg.Add(1)
go func(k string) {
defer wg.Done()
for j := 0; j < 100; j++ {
// 原子更新 key 的计数
val, _ := counter.LoadOrStore(k, 0)
counter.Store(k, val.(int)+1)
}
}(key)
}
}
wg.Wait()
// 打印结果
counter.Range(func(k, v any) bool {
fmt.Printf("Key: %s, Count: %d\n", k, v)
return true
})
}
五、输出示例:
yaml
Key: A, Count: 1000
Key: B, Count: 1000
Key: C, Count: 1000
六、常用方法介绍:
方法 | 功能描述 |
---|---|
Load(key) |
获取键对应的值和是否存在 |
Store(key, value) |
设置键值 |
LoadOrStore(key, value) |
获取已存在值,否则存入新值 |
Delete(key) |
删除指定键值对 |
Range(func(k, v any) bool) |
遍历所有键值对,返回 false 可中断遍历 |
七、注意事项
-
无法使用
m["key"]
形式 ,只能通过Load
/Store
; -
LoadOrStore
非原子加法操作 :如本例中val + 1
不是并发安全操作;- 更好的方式是配合原子操作
sync/atomic
或引入锁;
- 更好的方式是配合原子操作
-
sync.Map
更适合"读多写少"的场景; -
若是写多场景,建议仍用
RWMutex + map
自行控制。
八、应用场景举例
- 统计类服务:如 PV/UV 实时统计;
- 缓存系统:缓存热点数据;
- 注册中心:维护在线服务列表;
- 连接池管理:存储连接状态;
九、总结
sync.Map
提供了一种简洁、高效的方式来处理并发情况下的共享数据。它避免了手动加锁的繁琐,让我们更专注于业务逻辑。
✅ 并发安全 ✅ 使用简单 ✅ 性能优越(适合读多写少)
下一个实战案例:我们将结合
context + sync.Map
实现带过期控制的并发缓存服务,敬请关注!
🔧 附加福利:你可以这样封装自己的线程安全结构
go
type SafeMap struct {
data sync.Map
}
func (sm *SafeMap) Incr(key string) {
for {
val, _ := sm.data.LoadOrStore(key, 0)
newVal := val.(int) + 1
sm.data.Store(key, newVal)
break
}
}