Go语言中map使用和并发安全详解

Go语言中map使用和并发安全详解

概述

在Go语言中,map是一种集合类型,它可以关联一个键和一个值。map是一种引用类型,可以使用 make 函数来创建。map 的底层实现是 hash 表,因此 map 的键是无序的,但是在迭代过程中,Go语言会自动对其进行排序。

map 的基本使用方法是:使用键访问值,如果键不存在,则会返回初始值。map 与 slice、struct 一样,可以使用 range 迭代器。同时,map 也支持删除元素,可以使用 delete 函数将指定元素从 map 中移除。

同时,由于 map 在并发条件下并不是线程安全的,因此在并发场景下使用 map 时需要使用加锁机制。

在本篇文章中,我们将详细讲解 Go 语言中 map 的基本使用方法以及在并发场景下如何保证 map 的安全。

map 实现与基本使用

创建 map 的语法为 make(map[keyType]valueType)。其中,keyType 是键的类型,valueType 是值的类型,例如:

// 创建键为 string 类型,值为 int 类型的 map
m := make(map[string]int)

向 map 中添加元素同样简单:

// 向 map 中添加元素
m["one"] = 1
m["two"] = 2

通过键访问值:

// 通过键访问值
fmt.Println(m["one"])
fmt.Println(m["two"])

输出:

1
2

当访问一个不存在的键时,会返回 map 中值类型的初始值:

fmt.Println(m["three"])

输出:

0

可以使用 format 包中提供的 %v 格式化输出 map 的元素:

// 使用迭代器遍历 map
for k, v := range m {
  fmt.Printf("key=%v, value=%v\n", k, v)
}

输出:

key=one, value=1
key=two, value=2

删除元素:

delete(m, "one")

map 也支持长度操作:

len(m)

map 的值可以是任意类型,它还可以包含另一个 map,不过 map 不可以自己包含自己,否则会引起死循环。

并发安全

由于 map 在并发条件下并不是线程安全的,因此在并发场景下使用 map 时需要使用加锁机制,以防止不同的 goroutine 并发访问同一个 map 导致的数据竞争和内存问题。

下面是一个使用 sync 包的方法:

import "sync"

// 创建互斥锁
var mutex sync.Mutex

// 安全的访问 map
func SafeAccessMap() {
  // 加锁
  mutex.Lock()
  defer mutex.Unlock()

  m["one"] = 1
  m["two"] = 2
  delete(m, "two")
}

也可以使用 sync 包中的 RWMutex 实现读写锁:

import "sync"

// 创建读写锁
var rwMutex sync.RWMutex

// 安全的访问 map
func SafeAccessMap() {
  // 写锁
  rwMutex.Lock()
  defer rwMutex.Unlock()

  m["one"] = 1
  m["two"] = 2
  delete(m, "two")
}

// 安全的迭代 map
func SafeIterateMap() {
  // 读锁
  rwMutex.RLock()
  defer rwMutex.RUnlock()

  for k, v := range m {
    fmt.Printf("key=%v, value=%v\n", k, v)
  }
}

以上是两个简单的示例,通过使用 mutex 或 RWMutex 实现了对 map 的访问和迭代的互斥操作,保证了 map 在并发访问时的安全性。

示例

示例 1:安全的并发计数器

下面是一个使用 sync.Mutex 实现的安全的并发计数器的示例:

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

// 创建互斥锁
var mutex sync.Mutex

// 计数变量
var count int

// 安全的增加计数器的函数
func SafeIncrement() {
  // 加锁
  mutex.Lock()
  defer mutex.Unlock()

  count++
}

// 返回计数器的函数
func GetCount() int {
  // 加锁
  mutex.Lock()
  defer mutex.Unlock()

  return count
}

func main() {
  // 并发执行的次数
  num := 1000

  // 创建 WaitGroup
  var wg sync.WaitGroup

  // 增加计数器
  for i := 0; i < num; i++ {
    wg.Add(1)

    go func() {
      defer wg.Done()

      SafeIncrement()
    }()
  }

  // 等待所有 goroutine 执行完毕
  wg.Wait()

  // 输出计数器值
  fmt.Println("count:", GetCount())
}

输出:

count: 1000

示例 2:并发安全的 LRU Cache

下面是一个使用 sync.Mutex 实现的并发安全的 LRU Cache 的示例:

import (
  "container/list"
  "sync"
)

// 缓存项
type LRUCacheItem struct {
  key   interface{}
  value interface{}
}

// LRU Cache
type LRUCache struct {
  // 最大存储空间
  maxBytes int

  // 当前已使用空间
  usedBytes int

  // 存储缓存项的 hashmap
  items map[interface{}]*list.Element

  // 双向链表,存储缓存项
  list *list.List

  // 互斥锁
  mutex sync.Mutex
}

// 构造函数
func NewLRUCache(maxBytes int) *LRUCache {
  return &LRUCache{
    maxBytes: maxBytes,
    items:    make(map[interface{}]*list.Element),
    list:     list.New(),
  }
}

// 添加缓存项
func (c *LRUCache) Add(key, value interface{}, bytes int) {
  // 加锁
  c.mutex.Lock()
  defer c.mutex.Unlock()

  // 如果缓存项已存在,则更新值和链表顺序
  if ele, ok := c.items[key]; ok {
    c.usedBytes += bytes - ele.Value.(*LRUCacheItem).value.(int)
    ele.Value.(*LRUCacheItem).value = value
    c.list.MoveToFront(ele)
    return
  }

  // 创建缓存项
  item := &LRUCacheItem{key, value}

  // 在链表头部插入缓存项
  ele := c.list.PushFront(item)

  // 在 hashmap 中存储缓存项,并将 hashmap 中删除超出存储空间的第一个缓存项
  c.items[key] = ele
  c.usedBytes += bytes
  for c.usedBytes > c.maxBytes && c.list.Len() > 0 {
    c.removeOldestItem()
  }
}

// 获取缓存项
func (c *LRUCache) Get(key interface{}) interface{} {
  // 加锁
  c.mutex.Lock()
  defer c.mutex.Unlock()

  // 如果缓存项已存在,则更新链表顺序,并返回值
  if ele, ok := c.items[key]; ok {
    c.list.MoveToFront(ele)
    return ele.Value.(*LRUCacheItem).value
  }

  // 如果缓存项不存在,则返回 nil
  return nil
}

// 删除缓存项
func (c *LRUCache) Remove(key interface{}) {
  // 加锁
  c.mutex.Lock()
  defer c.mutex.Unlock()

  // 如果缓存项已存在,则删除缓存项
  if ele, ok := c.items[key]; ok {
    c.removeElement(ele)
  }
}

// 删除最老的缓存项
func (c *LRUCache) removeOldestItem() {
  ele := c.list.Back()
  if ele != nil {
    c.removeElement(ele)
  }
}

// 删除链表节点和 hashmap 中对应的缓存项
func (c *LRUCache) removeElement(ele *list.Element) {
  c.list.Remove(ele)

  item := ele.Value.(*LRUCacheItem)
  c.usedBytes -= item.value.(int)

  delete(c.items, item.key)
}

以上示例实现了一个线程安全的 LRU Cache。其基于哈希表和双向链表实现,可以进行常用的添加、获取和删除操作。同时,为了保证线程安全,使用 sync.Mutex 实现了对 Cache 的互斥访问。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Go语言中map使用和并发安全详解 - Python技术站

(0)
上一篇 2023年6月26日
下一篇 2023年6月26日

相关文章

  • linux-expr:cygwin中的非整数参数错误

    在Cygwin中,当使用expr命令进行数学计算时,可能会遇到“expr: non-integer argument”错误。这个错误通常是由于使用了非整数参数而引起的。本文将提供完整的攻略,解决这个问题,并提供两个示例说明。 步骤1:检查参数是否为整数 首先,我们需要检查使用的参数是否为整数。expr命令只能处理整数,如果使用了非整数参数,则会出现“expr…

    other 2023年5月8日
    00
  • ORACLE SQL语句优化技术分析

    ORACLE SQL语句优化技术分析完整攻略 简介 SQL语句是数据库关键操作指令之一,一旦SQL语句存在性能问题,就会导致数据库操作效率低下、响应缓慢等问题,因此优化SQL语句十分重要。本文将介绍ORACLE SQL语句优化的相关技术和分析方法,完整攻略如下: SQL语句优化技术 查询计划分析技术 查询计划是涉及到数据库查询优化的核心问题之一,通过查询计划…

    other 2023年6月25日
    00
  • 浅谈iOS关于头文件的导入问题

    浅谈iOS关于头文件的导入问题 在iOS开发中,头文件的导入是一个非常重要的问题。正确导入头文件是程序成功编译的先决条件,而错误的导入方式可能导致编译错误甚至是程序崩溃。本文将从两个方面介绍如何正确导入头文件:如何正确导入框架中的头文件,以及如何正确导入自定义的头文件。 如何正确导入框架中的头文件 对于许多开发者来说,导入框架中的头文件应该是最常见的问题之一…

    other 2023年6月27日
    00
  • css控制元素上下左右居中

    CSS控制元素上下左右居中 在前端开发中,控制元素在页面中居中是一个经常被提及的问题。本文将介绍几种使用CSS控制元素上下左右居中的方法。 1. 使用flex布局 现代CSS有很多可以将元素居中的方法,其中使用flexbox布局是应用最为广泛的方式之一。在使用前,请确保目标元素的父元素被设置为 display: flex。 .parent { display…

    其他 2023年3月29日
    00
  • C语言学习之函数知识总结

    C语言学习之函数知识总结 函数的定义和调用 函数是指一段封装好的代码块,可以做特定的任务或者返回一个值。在C语言中,函数可以通过以下方式定义: 返回值类型 函数名(参数1类型 参数1名, 参数2类型 参数2名, …) { //函数体 return 返回值; } 其中,返回值类型指的是函数计算出来的结果的类型,参数1类型和参数2类型指的是传递给函数的参数的…

    other 2023年6月27日
    00
  • osg + cuda

    以下是osg+cuda的完整攻略,包含osg和cuda的基本介绍、osg中使用cuda的方法、以及两个示例说明。 OSG+cuda的介绍 OpenSceneGraph(OSG)是开源的3D图形引擎,支持多种平台和多种编程语言。CUDA是NVIDIA开发的一种并行计算平台和编程模型,用于GPU加速计算。OSG+cuda的组合可以实现高效的3D图形渲染和GPU加…

    other 2023年5月7日
    00
  • qt两种按钮点击事件应用

    以下是使用Qt实现两种按钮点击事件的完整攻略,包含两个示例说明: 步骤1:创建Qt项目 首先,您需要一个Qt项目。您可以使用以下步骤创建Qt项目: 打开Qt Creator并单击“New Project”按钮。 选择“Qt Widgets Application”选项,并选择您要创建的项目类型(例如,Main Window)。 输入项目名称和路径,并单击“N…

    other 2023年5月6日
    00
  • C语言基础知识点解析(extern,static,typedef,const)

    关于C语言基础知识点解析的完整攻略,我将分为四个部分来详细讲解extern、static、typedef、const的定义、用法和示例。 1. extern详解 extern是外部变量或函数的声明关键字。若在一个文件中定义了一个全局变量或函数,而在另一个文件中需要使用该变量或函数,则必须在使用之前用extern进行声明,表示该变量或函数是外部可见的。 ext…

    other 2023年6月26日
    00
合作推广
合作推广
分享本页
返回顶部