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

相关推荐
lang201509281 天前
Spring Boot构建RESTful服务与Actuator监控
spring boot·后端·restful
向上的车轮1 天前
无需云服务的家庭相册:OpenHarmony 上的 Rust 实践
开发语言·后端·rust
程序猿小蒜1 天前
基于springboot的车辆管理系统设计与实现
java·数据库·spring boot·后端·spring·oracle
90后的晨仔1 天前
Java后端开发:从零构建企业级应用的完整架构与技术栈详解
后端
我命由我123451 天前
Spring Cloud - Spring Cloud 声明式接口调用(Fiegn 声明式接口调用概述、Fiegn 使用)
java·后端·spring·spring cloud·微服务·架构·java-ee
canonical_entropy1 天前
领域驱动设计(DDD)中聚合根的最主要职责真的是维护一致性吗?
后端·架构·领域驱动设计
AntBlack1 天前
不当韭菜 : 好像真有点效果 ,想藏起来自己用了
前端·后端·python
橙子家1 天前
Serilog 日志库的简介
后端
间彧1 天前
Java线程池深度实战:不同场景下的最优选择与性能优化
后端
间彧1 天前
CompletableFuture与线程池:并发编程的双剑合璧
后端