线程安全SyncMap使用示例
1 简介特点
sync.Map的超能力:
速度、安全、简单
因此,常规map会崩溃,锁定会减慢您的速度。sync.Map有什么秘密
2 三大优势:
无锁阅读:Goroutines 可以像吃到饱的自助餐一样阅读——无需等待,无需锁定。非常适合高流量查找。
读取密集型冠军:专为读取数量超过写入次数(如 70%+)的场景而构建。写入会受到影响,但读取会飞。
Slick API: StoreLoad, LoadOrStore, Range, 这是没有样板的线程安全魔法。
在隐藏的位置下,它有一个巧妙的拆分:只读的“快速通道”(原子驱动)和繁杂的“写入通道”(需要时锁定)。没有读取锁定,只有纯粹的速度。以下是它的实际效果:
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
v, loaded := m.LoadOrStore("key", 42)
fmt.Printf("Value: %v, Was Loaded? %t\n", v, loaded) // 42, false
v, loaded = m.LoadOrStore("key", 100)
fmt.Printf("Value: %v, Was Loaded? %t\n", v, loaded) // 42, true
}
LoadOrStore是黄金——一举检查和设置。想要安全地循环吗? 有你的支持:Range
m.Store("a", 1)
m.Store("b", 2)
m.Range(func(key, value interface{}) bool {
fmt.Printf("%v: %v\n", key, value)
return true // Keep going; false to stop
})
它就像一个并发作弊代码——快速、安全且对开发人员友好。让我们看看它在野外大放异彩。
sync.Map优势点:缓存和任务技巧
理论很酷,但代码实现才是它的优势地方。以下是我的围棋冒险中两个经过实战考验的用例。
缓存它
想象一下,一个用户服务有无数个请求。每次都打数据库?不需要!我们可以使用 sync.Map缓存它 :
package main
import (
"fmt"
"sync"
"time"
)
type Cache struct {
data sync.Map
}
func (c *Cache) GetOrSet(key string, fetch func() string) string {
if v, ok := c.data.Load(key); ok {
return v.(string)
}
v := fetch()
c.data.Store(key, v)
return v
}
func main() {
cache := Cache{}
fetch := func() string {
time.Sleep(100 * time.Millisecond) // Fake DB lag
return "user123"
}
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(cache.GetOrSet("user:123", fetch))
}()
}
wg.Wait()
}
为什么它很震撼:无锁读取意味着 goroutines 可以快速通过。专业提示:需要添加过期时间戳结构体——sync.Map不会为你做过期清理这件事。
任务状态操控
跟踪异步任务?在数千个 goroutine 中思考“待定”到“完成”:
package main
import (
"fmt"
"sync"
"time"
)
type TaskManager struct {
tasks sync.Map
}
func (tm *TaskManager) Set(id string, status string) {
tm.tasks.Store(id, status)
}
func (tm *TaskManager) Get(id string) (string, bool) {
v, ok := tm.tasks.Load(id)
return v.(string), ok
}
func main() {
tm := TaskManager{}
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
id := fmt.Sprintf("task%d", i)
go func() {
defer wg.Done()
tm.Set(id, "pending")
time.Sleep(50 * time.Millisecond)
tm.Set(id, "done")
}()
}
wg.Wait()
if v, ok := tm.Get("task1"); ok {
fmt.Println(v) // "done"
}
}
明白了:我曾经删除过密钥并得到了时髦的结果——是快照,而不是实时锁。修复Range:先收集密钥,后删除Range:
var toDelete []interface{}
tm.tasks.Range(func(key, value interface{}) bool {
if value == "done" {
toDelete = append(toDelete, key)
}
return true
})
for _, key := range toDelete {
tm.tasks.Delete(key)
}
这些优势显示了灵活性——读取尖叫,sync.Map写入保持安全。
大师:要避免sync.Map的最佳操作。
sync.Map是一头野兽,但挥舞不当,它就会咬人。这是我的围棋战争故事中的备忘单。
3 更专业的操控
选择你的战斗:这是一个读取量很大的操作——想想 70%+ 的读取(缓存、状态查找)。写作繁重? sync.Mutex可能会把它淘汰出局。
类型安全包装器:interface{}是潘多拉魔盒。总结一下:
type SafeMap struct {
m sync.Map
}
func (s *SafeMap) Set(key string, value int) {
s.m.Store(key, value)
}
func (s *SafeMap) Get(key string) (int, bool) {
v, ok := s.m.Load(key)
if !ok {
return 0, false
}
return v.(int), true
}
不再有类型断言恐慌——甜蜜的解脱。
零准备:不废话——只需声明即可:
make()
var m sync.Map
m.Store("key", 42)
- 要规避的劣势
范围操作:删除 ?混乱随之而来——这是一张快照,而不是一把锁。修复:收集,然后电击(见上文)。
Range写入量大,换成了写入量大的日志系统——延迟激增。
sync.Map写入同步双层,这很痛苦。
sync.Mutex修复:如果写入达到 30%+,则坚持使用sync.Mutex。
类型混沌:缓存中的混合类型?恐慌之城。修复:构建它:
type Entry struct {
Value int
}
var m sync.Map
m.Store("key", Entry{Value: 42})
真实谈话:在电子商务应用程序中,在促销期间出现故障——库存更新太多。
切换到 sync.Map,然后砰——延迟从 50 毫秒下降到 10 毫秒。伙计们,将工具sync.Mutex与map相匹配工作。
4 sync.Map与并发工具集
sync.Map不是镇上唯一的游戏。让我们与并发小队进行对比。
经典:sync.RWMutex和sync.Mutex
同步。Mutex:锁定所有内容 - 简单、易于写入,但读取爬行。
同步。RWMutex:读取免费运行,写入等待。中等并发性为固体。
快速比较:
工具 读取速度 写入速度 最佳氛围
sync.Mutex + map 慢 体面 写量大,不费吹灰之力
sync.RWMutex + map 好 慢 读取量大、冷流量
sync.Map 炽烈 好 读取量大的 goroutine 风暴
战争故事:一个滞后为 100 毫秒的命令系统。 sync.Mutex将其缩短到 50 毫秒,
但是 sync.RWMutex?15毫秒。
sync.Map无锁读取 FTW。
4 第三方重大优势
-
- 单程 ( sync singleflight)
内容:一个 goroutine 获取,其他 goroutine 等待——非常适合缓存未命中。
sync.Map组队:将其与以下产品配对:
var g singleflight.Group
var m sync.Map
func getStuff(key string) string {
if v, ok := m.Load(key); ok {
return v.(string)
}
v, _, _ := g.Do(key, func() (interface{}, error) {
return "data", nil
})
m.Store(key, v)
return v.(string)
}
内容:分片map - 每个分片的锁。
为什么:在写入时杀死它,在读取时表现不错。
时间:写作繁重的混乱。
工具箱快照:
工具 读取速度 写入速度 最适合
sync.Map 顶级 中间 阅读繁重的疯狂
singleflight 不适用 不适用 缓存错过英雄
concurrent-map 体面 恒定的 写入量大的平衡
选择你的工具
读取量大、流量大:。sync.Map
写量大,简单:。sync.Mutex
缓存未命中:+ 。sync.Mapsingleflight
写入量大、可扩展:.concurrent-map
sync.Map是你的速度利器,但工程师们有选择。明智地做出其他选择!
sync.Map:您的并发机
5 小结
我们已经从map的崩溃到精通。它是 Go 自 1.9 以来的并发王牌——无锁读取、流畅的 API 以及处理读取密集型混乱的诀窍。
sync.Map这种双层技巧(读取映射、脏映射)使读取尖叫和写入安全。但它不是写入繁重的向导——知道它的局限性。
在键盘上纹身的要点
读取规则:70%+ 读取?sync.Map是你的 MVP——缓存、状态跟踪器,凡是你能想到的。
包装它:使用自定义结构或interface{}包装器消除痛苦。
范围优势:收集钥匙,然后删除——不要在现场弄乱它。
混合使用:用于singleflight缓存未命中或concurrent-map写入并发战争。
真正的交易教训:在黑色星期五的高峰期间,我扔了一个库存系统——写入sync.Map杀死了它。切换到sync.Mutex ,延迟直线下降。这是一辆读取操作的跑车,而不是写入装载的自卸卡车——正确使用它:sync.Map。
- 点赞
- 收藏
- 关注作者
评论(0)