Go语言实战案例:使用sync.Map构建线程安全map

在并发编程中,共享资源的访问是一个绕不开的问题。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 类型并不支持并发读写。


二、解决方式有哪些?

  1. 使用 sync.Mutex + map 手动加锁;
  2. 使用 sync.RWMutex 读写分离锁;
  3. 使用 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 可中断遍历

七、注意事项

  1. 无法使用 m["key"] 形式 ,只能通过 Load / Store;

  2. LoadOrStore 非原子加法操作 :如本例中 val + 1 不是并发安全操作;

    • 更好的方式是配合原子操作 sync/atomic 或引入锁;
  3. sync.Map 更适合"读多写少"的场景;

  4. 若是写多场景,建议仍用 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
    }
}

相关推荐
回家路上绕了弯9 分钟前
让 AI 成为团队的共同能力
后端
70后写java的老码农18 分钟前
一个 starter 通吃 9 家主流大模型:Spring Boot 3.x + Spring AI 1.x 极简落地
后端
武子康43 分钟前
MultiNet 只听到一次命令,为什么连续触发?
人工智能·后端·agent
挖掘狂人1 小时前
同一个端口能不能被两个程序同时监听?
java·后端·程序员
codigger1 小时前
同一个端口能不能被两个程序同时监听?
java·后端·程序员·编程
2501_933923251 小时前
SpringCloud:OpenFeign快速实现
java·后端·spring cloud·openfeign
拖孩1 小时前
AI 帮我写的小程序上线 44 天:36 个人进了收银台,8 个人付钱
前端·后端·微信小程序
Rain的Java大神之路1 小时前
我,13年Java老兵,建了个免费网站,公开叫板“Java已死”:没有广告,没有付费墙,151个页面全是踩坑干货
java·后端·程序员
萧瑟余晖1 小时前
Spring Integration企业集成概览详解
java·后端·spring
小小张说故事2 小时前
Python 的赋值为什么不会复制对象?拆开引用、浅拷贝与深拷贝
后端·python