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技术站