0

0

Go 并发安全 Map 使用指南

聖光之護

聖光之護

发布时间:2025-09-07 16:30:02

|

598人浏览过

|

来源于php中文网

原创

go 并发安全 map 使用指南

本文旨在阐述在 Go 语言并发环境下使用 Map 的正确姿势。重点讲解在读写并发的场景下,如何保证 Map 的数据安全,以及如何通过互斥锁(Mutex)来实现并发安全的 Map 访问。我们将通过示例代码和注意事项,帮助你更好地理解和应用并发安全的 Map。

并发 Map 的数据竞争问题

在 Go 语言中,内置的 map 类型并非线程安全。这意味着,如果在多个 goroutine 中同时读写同一个 map,可能会导致数据竞争,进而引发程序崩溃或其他未定义行为。

以下几种情况需要特别注意:

  • 多个读者,没有写者: 这种情况是安全的,可以并发读取。
  • 一个写者,没有读者: 这种情况也是安全的,写操作可以正常进行。
  • 至少一个写者,且至少一个读者或写者: 在这种情况下,所有读者和写者都必须使用同步机制来访问 map。

使用互斥锁(Mutex)实现并发安全 Map

最常用的方法是使用 sync.Mutex 来保护 map 的读写操作。 sync.RWMutex 提供了更细粒度的控制,允许并发读取,但仍然需要互斥写入。

使用 sync.Mutex

package main

import (
    "fmt"
    "sync"
    "time"
)

type ConcurrentMap struct {
    sync.Mutex
    data map[string]int
}

func NewConcurrentMap() *ConcurrentMap {
    return &ConcurrentMap{
        data: make(map[string]int),
    }
}

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

func (m *ConcurrentMap) Get(key string) (int, bool) {
    m.Lock()
    defer m.Unlock()
    val, ok := m.data[key]
    return val, ok
}

func (m *ConcurrentMap) Delete(key string) {
    m.Lock()
    defer m.Unlock()
    delete(m.data, key)
}

func main() {
    cmap := NewConcurrentMap()

    var wg sync.WaitGroup

    // 启动多个 goroutine 并发写入
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            key := fmt.Sprintf("key-%d", i)
            cmap.Set(key, i*10)
            time.Sleep(time.Millisecond * 10) // 模拟一些耗时操作
        }(i)
    }

    // 启动多个 goroutine 并发读取
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            key := fmt.Sprintf("key-%d", i)
            val, ok := cmap.Get(key)
            if ok {
                fmt.Printf("Goroutine %d: key=%s, value=%d\n", i, key, val)
            } else {
                fmt.Printf("Goroutine %d: key=%s not found\n", i, key)
            }
            time.Sleep(time.Millisecond * 5) // 模拟一些耗时操作
        }(i)
    }

    wg.Wait() // 等待所有 goroutine 完成
    fmt.Println("Done.")
}

代码解释:

网纪互联工作室公文签收系统司法版20130919
网纪互联工作室公文签收系统司法版20130919

公文签收系统采用ASP+ACCESS开发的一套具有方便、快速、安全、高效的公文签收系统。本系统功能完备、使用方便快捷,已在全国各地的政府、司法、教育等部门成功应用,并得到了多方一致好评。本系统从公文的发布、查阅、签收、反馈、修改、删除等操作都将采用独立方式认证,确保系统安全稳定运行。 网纪互联公文签收系统功能简介: 1. 发布公文:可以选择所有人或指定部门、个人进行签收或无需签收。2. 公文类型:

下载
  1. ConcurrentMap 结构体包含一个 sync.Mutex 和一个 map[string]int。
  2. Set、Get 和 Delete 方法在访问 map 之前都会先获取锁,并在操作完成后释放锁,保证了并发安全。
  3. main 函数启动多个 goroutine 并发读写 ConcurrentMap,使用 sync.WaitGroup 等待所有 goroutine 完成。

使用 sync.RWMutex

package main

import (
    "fmt"
    "sync"
    "time"
)

type ConcurrentMap struct {
    sync.RWMutex
    data map[string]int
}

func NewConcurrentMap() *ConcurrentMap {
    return &ConcurrentMap{
        data: make(map[string]int),
    }
}

func (m *ConcurrentMap) Set(key string, value int) {
    m.Lock() // 使用写锁
    defer m.Unlock()
    m.data[key] = value
}

func (m *ConcurrentMap) Get(key string) (int, bool) {
    m.RLock() // 使用读锁
    defer m.RUnlock()
    val, ok := m.data[key]
    return val, ok
}

func (m *ConcurrentMap) Delete(key string) {
    m.Lock() // 使用写锁
    defer m.Unlock()
    delete(m.data, key)
}

func main() {
    cmap := NewConcurrentMap()

    var wg sync.WaitGroup

    // 启动多个 goroutine 并发写入
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            key := fmt.Sprintf("key-%d", i)
            cmap.Set(key, i*10)
            time.Sleep(time.Millisecond * 10) // 模拟一些耗时操作
        }(i)
    }

    // 启动多个 goroutine 并发读取
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            key := fmt.Sprintf("key-%d", i)
            val, ok := cmap.Get(key)
            if ok {
                fmt.Printf("Goroutine %d: key=%s, value=%d\n", i, key, val)
            } else {
                fmt.Printf("Goroutine %d: key=%s not found\n", i, key)
            }
            time.Sleep(time.Millisecond * 5) // 模拟一些耗时操作
        }(i)
    }

    wg.Wait() // 等待所有 goroutine 完成
    fmt.Println("Done.")
}

代码解释:

  1. 与使用 sync.Mutex 的例子类似,但使用了 sync.RWMutex。
  2. Get 方法使用 RLock() 获取读锁,允许并发读取。
  3. Set 和 Delete 方法仍然使用 Lock() 获取写锁,保证写操作的互斥性。

注意事项

  • 锁的粒度: 锁的粒度会影响程序的性能。 锁的范围越小,并发性越高,但也会增加锁管理的开销。
  • 死锁: 避免死锁的发生。 确保锁的获取顺序一致,并且在持有锁的时候避免调用其他需要获取锁的函数。
  • defer 释放锁: 使用 defer 语句来确保锁在函数退出时总是被释放,避免锁泄漏。
  • 性能考量: 虽然互斥锁可以保证并发安全,但也会带来性能损耗。 在高并发场景下,可以考虑使用更高级的并发控制技术,例如分片 map、原子操作等。

使用 sync.Map

Go 1.9 引入了 sync.Map 类型,它是一种并发安全的 map 实现,无需显式加锁。 sync.Map 内部使用了更复杂的机制来减少锁的竞争,在高并发场景下可能比使用 sync.Mutex 的 map 性能更好。

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var m sync.Map

    var wg sync.WaitGroup

    // 启动多个 goroutine 并发写入
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            key := fmt.Sprintf("key-%d", i)
            m.Store(key, i*10)
            time.Sleep(time.Millisecond * 10) // 模拟一些耗时操作
        }(i)
    }

    // 启动多个 goroutine 并发读取
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            key := fmt.Sprintf("key-%d", i)
            val, ok := m.Load(key)
            if ok {
                fmt.Printf("Goroutine %d: key=%s, value=%v\n", i, key, val)
            } else {
                fmt.Printf("Goroutine %d: key=%s not found\n", i, key)
            }
            time.Sleep(time.Millisecond * 5) // 模拟一些耗时操作
        }(i)
    }

    wg.Wait() // 等待所有 goroutine 完成
    fmt.Println("Done.")
}

代码解释:

  1. 直接使用 sync.Map 类型,无需手动创建 map。
  2. 使用 Store 方法写入数据,使用 Load 方法读取数据。
  3. sync.Map 提供了 LoadOrStore、Delete、Range 等方法,可以根据实际需求选择使用。

sync.Map 的适用场景

  • 读多写少: sync.Map 在读多写少的场景下性能较好。
  • key 不频繁变化: sync.Map 针对 key 的增删改查操作做了优化,但如果 key 频繁变化,性能可能会下降。

总结

在 Go 语言并发编程中,确保 map 的并发安全至关重要。 可以使用 sync.Mutex 或 sync.RWMutex 来保护 map 的读写操作,也可以使用 Go 1.9 引入的 sync.Map 类型。 选择哪种方法取决于具体的应用场景和性能需求。 务必注意锁的粒度、死锁避免和性能考量,编写健壮且高效的并发程序。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

463

2023.08.02

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

463

2023.08.02

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

240

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

192

2025.07.04

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

463

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

544

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

113

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

200

2025.08.29

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

14

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 4.4万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号