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

相关推荐
洛卡卡了22 分钟前
面试官问限流降级,我项目根本没做过,咋办?
后端·面试·架构
小厂永远得不到的男人23 分钟前
一个菜鸟程序员的真实体验:为什么我在国内用Trae更顺手?
程序员·trae
ezl1fe41 分钟前
RAG 每日一技(十四):化繁为简,统揽全局——用LangChain构建高级RAG流程
人工智能·后端·算法
amazingCompass1 小时前
Java 开发必备技能:深入理解与实战 IntelliJ IDEA 中的 VM Options
后端
欧的曼1 小时前
cygwin+php教程(swoole扩展+redis扩展)
开发语言·redis·后端·mysql·nginx·php·swoole
巴拉巴巴巴拉1 小时前
Spring Boot 整合 Thymeleaf
java·spring boot·后端
用户1512905452202 小时前
Docker部署 Alist
后端
白应穷奇2 小时前
Diesel的高性能特性: 深入理解Rust ORM的性能优化
后端·rust
用户1512905452202 小时前
HDOJ-ACM1017(JAVA)
后端
Ray662 小时前
OOP 四大特征
后端