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

相关推荐
brzhang43 分钟前
AI Agent 干不好活,不是它笨,告诉你一个残忍的现实,是你给他的工具太难用了
前端·后端·架构
brzhang1 小时前
一文说明白为什么现在 AI Agent 都把重点放在上下文工程(context engineering)上?
前端·后端·架构
Roye_ack1 小时前
【项目实战 Day9】springboot + vue 苍穹外卖系统(用户端订单模块 + 商家端订单管理模块 完结)
java·vue.js·spring boot·后端·mybatis
AAA修煤气灶刘哥3 小时前
面试必问的CAS和ConcurrentHashMap,你搞懂了吗?
后端·面试
SirLancelot13 小时前
MinIO-基本介绍(一)基本概念、特点、适用场景
后端·云原生·中间件·容器·aws·对象存储·minio
golang学习记4 小时前
Go 1.25 新特性:正式支持 Git 仓库子目录作为 Go 模块
后端
Penge6664 小时前
一文读懂 ucrypto.Md5
后端
Terio_my7 小时前
Spring Boot 缓存集成实践
spring boot·后端·缓存
karry_k7 小时前
JMM与Volatitle
后端
数字化顾问7 小时前
“AMQP协议深度解析:消息队列背后的通信魔法”之核心概念与SpringBoot落地实战
开发语言·后端·ruby