线程安全SyncMap使用示例

举报
码乐 发表于 2025/08/04 09:03:17 2025/08/04
【摘要】 1 简介特点sync.Map的超能力: 速度、安全、简单因此,常规map会崩溃,锁定会减慢您的速度。sync.Map有什么秘密 2 三大优势:无锁阅读:Goroutines 可以像吃到饱的自助餐一样阅读——无需等待,无需锁定。非常适合高流量查找。读取密集型冠军:专为读取数量超过写入次数(如 70%+)的场景而构建。写入会受到影响,但读取会飞。Slick API: StoreLoad, L...

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 第三方重大优势

    1. 单程 ( 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。

【版权声明】本文为华为云社区用户翻译文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容, 举报邮箱:cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。