Go 中的并发 Map:深入探索 sync.Map 及其他实现方法

在 Go 语言的并发编程世界中,数据共享和同步是永恒的话题。map 是 Go 语言中常用的数据结构,但在多 goroutine 环境中直接使用它并不是线程安全的。因此,我们需要采用特定的策略来确保并发访问的安全性。本文将深入探讨 Go 中的并发 Map,包括 sync.Map 的使用方法、实现原理、分片加锁策略以及无锁(lock-free)技术,帮助你在实际项目中做出最佳选择。

1. Go 中的并发 Map 概述

在 Go 中,原生的 map 类型不是线程安全的。如果多个 goroutine 同时读写同一个 map,将会引发数据竞态和潜在的程序崩溃。因此,在并发环境中使用 map 时,我们需要采用线程安全的实现。

1.1. 线程安全的 Map 实现方式

主要有以下几种方式来实现线程安全的 Map:

  • 使用 sync.Map:Go 标准库提供的并发 Map 实现。
  • 分片加锁:通过将 Map 划分为多个片段,每个片段使用独立的锁。
  • 无锁(lock-free):利用原子操作实现的 Map,通常比较复杂,但可以提升性能。

2. 使用 sync.Map

2.1. sync.Map 的概述

sync.Map 是 Go 标准库提供的并发安全 Map。它的主要特点包括:

  • 内部使用了读写分离策略,适合读多写少的场景。
  • 提供了原子操作,避免了复杂的锁机制。

2.2. sync.Map 的方法

sync.Map 提供了以下主要方法:

  • Store(key, value): 存储一个键值对。
  • Load(key): 根据键加载一个值。
  • LoadOrStore(key, value): 如果键存在,返回其值;否则存储新值并返回。
  • Delete(key): 删除指定的键。
  • Range(f func(key, value interface{}) bool): 遍历所有键值对。

2.3. 使用示例

以下是 sync.Map 的一个简单示例:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map
    // 存储键值对
    m.Store("foo", "bar")
    m.Store("baz", 42)

    // 加载并打印值
    if val, ok := m.Load("foo"); ok {
        fmt.Println(val) // 输出: bar
    }
}

3. 分片加锁策略

另一种实现线程安全的 Map 的方法是分片加锁。通过将 Map 划分为多个片段,每个片段使用独立的锁,可以降低锁竞争,提高并发性能。

3.1. 分片加锁的实现

package main

import (
    "fmt"
    "sync"
)

type ShardedMap struct {
    shards []map[string]int
    mu     []sync.RWMutex
}

func NewShardedMap(shardCount int) *ShardedMap {
    sm := &ShardedMap{
        shards: make([]map[string]int, shardCount),
        mu:     make([]sync.RWMutex, shardCount),
    }
    for i := range sm.shards {
        sm.shards[i] = make(map[string]int)
    }
    return sm
}

func (sm *ShardedMap) GetShardIndex(key string) int {
    return len(key) % len(sm.shards)
}

func (sm *ShardedMap) Set(key string, value int) {
    index := sm.GetShardIndex(key)
    sm.mu[index].Lock()
    defer sm.mu[index].Unlock()
    sm.shards[index][key] = value
}

func (sm *ShardedMap) Get(key string) (int, bool) {
    index := sm.GetShardIndex(key)
    sm.mu[index].RLock()
    defer sm.mu[index].RUnlock()
    value, ok := sm.shards[index][key]
    return value, ok
}

3.2. 分片加锁的优势

分片加锁的优势在于减少了锁竞争,每个片段可以独立地被多个 goroutine 安全访问。这种策略特别适用于写操作频繁的场景。

3.3. 分片加锁的注意事项

  • 分片数量的选择:分片数量不宜过多,以免增加内存开销和维护复杂度。
  • 均匀分布:确保键值对均匀分布在各个分片中,避免某些分片过载。

4. 无锁 Map 实现

无锁 Map 的实现通常基于原子操作,可以提高性能,但实现较复杂。下面是一个简单的无锁 Map 的思路。

4.1. 无锁 Map 的基本思路

无锁 Map 通常使用比较和交换(Compare and Swap, CAS)技术。Go 提供的 sync/atomic 包提供了原子操作支持。

4.2. 示例代码(简化版本)

package main

import (
    "fmt"
    "sync/atomic"
)

type Node struct {
    key   string
    value interface{}
    next  *Node
}

type LockFreeMap struct {
    head *Node
}

func NewLockFreeMap() *LockFreeMap {
    return &LockFreeMap{head: &Node{}}
}

// Store 存储键值对(简化实现)
func (m *LockFreeMap) Store(key string, value interface{}) {
    newNode := &Node{key: key, value: value}
    for {
        head := atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&m.head)))
        newNode.next = (*Node)(head)
        if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&m.head)), head, unsafe.Pointer(newNode)) {
            return
        }
    }
}

// Load 加载值(简化实现)
func (m *LockFreeMap) Load(key string) (interface{}, bool) {
    current := m.head.next
    for current != nil {
        if current.key == key {
            return current.value, true
        }
        current = current.next
    }
    return nil, false
}

4.3. 无锁 Map 的优势

无锁 Map 的优势在于避免了锁的开销,可以提高高并发场景下的性能。

4.4. 无锁 Map 的注意事项

  • 复杂度:无锁 Map 的实现相对复杂,需要深入理解原子操作和内存模型。
  • ABA 问题:需要考虑 ABA 问题,可能需要引入版本号或使用 sync/atomic 包中的其他原子操作。

5. 性能优化技巧

在实现高并发 Map 操作时,以下是一些性能优化技巧:

5.1. 选择合适的锁

使用互斥锁(sync.Mutex)或读写锁(sync.RWMutex)来保护 Map 的并发访问。

import (
    "sync"
)

type SafeMap struct {
    mu sync.RWMutex
    m  map[string]int
}

func (sm *SafeMap) Set(key string, value int) {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    sm.m[key] = value
}

func (sm *SafeMap) Get(key string) (int, bool) {
    sm.mu.RLock()
    defer sm.mu.RUnlock()
    value, ok := sm.m[key]
    return value, ok
}

5.2. 初始化容量

在创建 Map 时,合理预估容量可以减少扩容次数,提高性能。

m := make(map[string]int, 1000) // 预分配1000个槽位

5.3. 避免不必要的删除操作

删除操作可能会导致频繁的扩容和迁移,尽量减少不必要的删除。

5.4. 使用分片 Map

将数据分片存储在不同的 Map 中,减少锁的争用。

type ShardedMap struct {
    shards []map[string]int
}

func NewShardedMap(shardCount int) *ShardedMap {
    sm := &ShardedMap{
        shards: make([]map[string]int, shardCount),
    }
    for i := range sm.shards {
        sm.shards[i] = make(map[string]int)
    }
    return sm
}

func (sm *ShardedMap) GetShard(key string) *map[string]int {
    hash := fnv1aHash(key) % uint32(len(sm.shards))
    return &sm.shards[hash]
}

func fnv1aHash(key string) uint32 {
    // FNV-1a hash implementation
}

5.5. 使用原子操作

对于简单的计数器等场景,可以使用原子操作来避免锁的使用。

import (
    "sync/atomic"
)

var counter int64

func Increment() {
    atomic.AddInt64(&counter, 1)
}

6. 结论

通过上述方法,我们可以在 Go 中实现并发安全的 Map 操作,并优化性能。选择合适的并发 Map 实现方式,根据具体的应用场景和性能要求来决定使用 sync.Map、分片加锁还是无锁技术。

在实际应用中,sync.Map 通常是最容易实现和使用的选项,但它可能不适合所有场景。分片加锁和无锁 Map 提供了更多的灵活性和可能的性能优势,但也增加了实现的复杂度。作为开发者,我们需要根据具体的业务需求和性能测试结果来选择最合适的方案。

希望本文能帮助你更好地理解和使用 Go 中的并发 Map。如果你有任何疑问或需要进一步的讨论,欢迎在评论区留下你的问题。让我们一起探索 Go 语言的更多可能性!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/922552.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python 抓取笑话内容并存入 CSV

在互联网上,有许多有趣的内容等待我们去挖掘和收集。今天,我们就来深入了解一段 Python 代码,它能够帮助我们从指定网站抓取笑话内容,并将其整理保存为 CSV 文件,方便后续查看和分析。 结果展示(文末附完整…

Redis-09 SpringBoot集成Redis

Jedis 和 lettuce 基本已经过时 集成RedisTemplate 单机 1.建 Modul 2.改pom <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instanc…

Linux:自定义Shell

本文旨在通过自己完成一个简单的Shell来帮助理解命令行Shell这个程序。 目录 一、输出“提示” 二、获取输入 三、切割字符串 四、执行指令 1.子进程替换 2.内建指令 一、输出“提示” 这个项目基于虚拟机Ubuntu22.04.5实现。 打开终端界面如图所示。 其中。 之前&#x…

夜天之书 #104 开源软件有断供的风险吗?

近期&#xff0c;Linux 上游因为受美国出口管制条例的影响&#xff0c;将移除部分开发者的 MAINTAINER 权限&#xff0c;引起了新一轮对开源依赖的重新评估。 关于其中开源精神和社群治理的讨论&#xff0c;卫 Sir 的两篇文章已经讨论得比较清楚&#xff08;见尾注&#xff09;…

tensorforce(dqn框架)安装

win7 64位操作系统 python版本&#xff1a;3.8.10 pip install tensorflow 默认的tensorflow的版本是2.31.0&#xff0c;安装tensorforce后自动升级到3.6.0 tensorflow:升级到3.6.0 keras&#xff1a;升级到3.6.0 tensorforce安装 pip3 install tensorforce protobuf 需要降到…

STM32抢占优先级不生效

板类型&#xff1a;STM32F103精英开发板代码背景&#xff1a; 设置了USART1中断和KEY_UP中断(使用EXTI0外部中断)两个中断的优先级分组都设为2&#xff08;2bit抢占优先级&#xff0c;2bit响应优先级)EXTI0中断抢占优先级设为3&#xff0c; 响应优先级设为3USART1抢占优先级设…

4.1_未授权漏洞

未授权漏洞 成因&#xff1a;配置错误&#xff0c;默认口令&#xff08;弱口令&#xff09;&#xff0c;接口配置不当&#xff1b;未授权漏洞 漏洞利用方式 Redis 未授权访问漏洞 Getshell方式 写入webshell&#xff1b; 连接目标redis&#xff1a;redis-cli -h 192.168.7…

快速识别模型:simple_ocr,部署教程

快速识别图片中的英文、标点符号、数学符号、Emoji, 模型会输出图片中文字行的坐标位置、最低得分、识别结果。当前服务用到的模型&#xff1a;检测模型、数字识别、英文符号识别。 一、部署流程 1.更新基础环境 apt update2.安装miniconda wget https://repo.anaconda.com/…

衡山派D133EBS 开发环境安装及SDK编译烧写镜像烧录

1.创建新文件夹&#xff0c;用来存放SDK包&#xff08;其实本质就是路径要对就ok了&#xff09;&#xff0c;右键鼠标通过Open Git Bash here来打开git 输入命令 git clone --depth1 https://gitee.com/lcsc/luban-lite.git 来拉取&#xff0c;如下所示&#xff1a;&#xff0…

蓝桥杯不知道叫什么题目

小蓝有一个整数&#xff0c;初始值为1&#xff0c;他可以花费一些代价对这个整数进行变换。 小蓝可以花贵1的代价将教数增加1。 小蓝可以花费3的代价将整数增加一个值,这个值是整数的数位中最大的那个(1到9) .小蓝可以花费10的代价将整数变为原来的2倍, 例如&#xff0c;如果整…

读取mysql、kafka数据筛选后放入mysql

要求&#xff1a; 从kafka的topic-car中读取卡口数据&#xff0c;将超速车辆写入mysql的t_monitor_info表 当通过卡口的车速超过该卡口限速的1.2倍 就认定为超速。 G107 1&#xff09;卡口数据格式如下&#xff1a; action_time long --摄像头拍摄时间戳&#xff0c;精确到秒…

CVE-2022-24124

根据提示 访问api/get-organizations salmap和手工注入都不行&#xff0c;使用substring() 查库&#xff0c;查到有4个库 ?p1&pageSize10&valuee99nb&sortField&sortOrder&field (substring((select count(schema_name) from information_schema.sche…

采用python3.12 +django5.1 结合 RabbitMQ 和发送邮件功能,实现一个简单的告警系统 前后端分离 vue-element

一、开发环境搭建和配置 #mac环境 brew install python3.12 python3.12 --version python3.12 -m pip install --upgrade pip python3.12 -m pip install Django5.1 python3.12 -m django --version #用于检索系统信息和进程管理 python3.12 -m pip install psutil #集成 pika…

Python文件夹.idea的作用

每当我们创建python的时候&#xff0c;发现文件夹里面都会有.idea文件夹。 那么这个是什么东西呢&#xff1f; .idea是集成开发环境&#xff08;IDE&#xff09;创建项目时自动生成的配置目录。 .idea文件目录介绍&#xff1a; workspace.xml&#xff1a;包含项目的整体配置信…

【计算机网络】多路转接之poll

poll也是一种linux中的多路转接方案(poll也是只负责IO过程中的"等") 解决&#xff1a;1.select的fd有上限的问题&#xff1b;2.每次调用都要重新设置关心的fd 一、poll的使用 int poll(struct pollfd *fds, nfds_t nfds, int timeout); ① struct pollfd *fds&…

使用 Elastic 收集 Windows 遥测数据:ETW Filebeat 输入简介

作者&#xff1a;来自 Elastic Chema Martinez 在安全领域&#xff0c;能够使用 Windows 主机的系统遥测数据为监控、故障排除和保护 IT 环境开辟了新的可能性。意识到这一点&#xff0c;Elastic 推出了专注于 Windows 事件跟踪 (ETW) 的新功能 - 这是一种强大的 Windows 原生机…

.net core MVC入门(一)

文章目录 项目地址一、环境配置1.1 安装EF core需要包1.2 配置数据库连接二、使用EF创建表2.1 整体流程梳理2.1 建表详细流程三、添加第一个视图3.1整体流程梳理3.1 添加视图,并显示在web里四、使用EF增加Catogory数据,并且读取数据到页面4.1整体流程梳理4.2 实现五、增加Cat…

短视频矩阵矩阵,矩阵号策略

随着数字媒体的迅猛发展&#xff0c;短视频平台已经成为企业和个人品牌推广的核心渠道。在这一背景下&#xff0c;短视频矩阵营销策略应运而生&#xff0c;它通过高效整合和管理多个短视频账号&#xff0c;实现资源的最优配置和营销效果的最大化。本文旨在深入探讨短视频矩阵的…

决策回归树【原理/算例/决策回归树 VS 线性回归】

决策回归树 1. 决策回归树原理2. 决策回归树算例3. 手动计算MSE和最优划分属性4. 决策回归树 VS 线性回归 1. 决策回归树原理 决策回归树&#xff0c;虽然叫做“回归”树&#xff0c;但是它的本质还是分类算法&#xff0c;只是分的类别多一点。 1. 回归树的裂分指标 回归树种&…

基于STM32的智能鱼缸控制系统的Proteus仿真

文章目录 一、智能鱼缸控制系统1.题目要求2.思路2.1 主控2.2 传感器2.3 按键2.4 声光报警2.5 自动换水&#xff0c;喂食&#xff0c;供氧2.6 OLED显示2.7 电源部分2.8 远程终端 3.电路仿真3.1 未仿真时3.2 开始仿真&#xff0c;正常显示3.3 按下设置按键&#xff0c;进入阈值界…