在并发编程中,数据共享和访问是一个重要的主题。go语言内置的map虽然高效,但并不是线程安全的。若在多线程环境中直接操作map,可能会引发并发写入的错误(fatal error: concurrent map writes)。因此,在需要并发访问map时,必须采取措施确保线程安全。
本文将介绍如何使用go语言的泛型和sync.rwmutex实现一个线程安全的map,同时支持常见的操作,例如增删改查、遍历和转化为普通的map。
1. 为什么需要线程安全的map
go语言内置的map在多线程环境中并不安全。例如,以下代码可能引发崩溃:
package main
import (
"fmt"
"sync"
)
func main() {
m := make(map[int]int)
var wg sync.waitgroup
for i := 0; i < 10; i++ {
wg.add(1)
go func(i int) {
defer wg.done()
m[i] = i
}(i)
}
wg.wait()
fmt.println(m)
}
运行上述代码可能会报错:fatal error: concurrent map writes。这是因为map的写操作没有加锁,在多线程中引发了竞态条件。
2. 如何实现线程安全的map
go标准库提供了sync.map,它是线程安全的。但它的api相对简单,缺乏泛型支持且性能在某些场景下并不理想。因此,我们可以基于sync.rwmutex和泛型封装一个自定义的线程安全map。
2.1 基本实现
以下是线程安全syncmap的完整实现:
package syncmap
import (
"sync"
)
// syncmap 定义了一个线程安全的泛型map
type syncmap[k comparable, v any] struct {
mu sync.rwmutex
m map[k]v
}
// newsyncmap 创建一个新的线程安全的syncmap
func newsyncmap[k comparable, v any]() *syncmap[k, v] {
return &syncmap[k, v]{
m: make(map[k]v),
}
}
// load 获取指定key的值,如果存在返回值和true,否则返回零值和false
func (s *syncmap[k, v]) load(key k) (v, bool) {
s.mu.rlock()
defer s.mu.runlock()
val, ok := s.m[key]
return val, ok
}
// store 设置指定key的值,如果key已存在会覆盖旧值
func (s *syncmap[k, v]) store(key k, value v) {
s.mu.lock()
defer s.mu.unlock()
s.m[key] = value
}
// has returns true if the key exists in the map.
func (s *syncmap[k, v]) has(key k) bool {
s.mu.rlock()
defer s.mu.runlock()
_, ok := s.m[key]
return ok
}
// delete 删除指定key的值
func (s *syncmap[k, v]) delete(key k) {
s.mu.lock()
defer s.mu.unlock()
delete(s.m, key)
}
// range 遍历所有的键值对,callback函数返回false时停止遍历
func (s *syncmap[k, v]) range(callback func(key k, value v) bool) {
s.mu.rlock()
defer s.mu.runlock()
for k, v := range s.m {
if !callback(k, v) {
break
}
}
}
// len returns the length of the map.
func (s *syncmap[k, v]) len() int {
s.mu.rlock()
defer s.mu.runlock()
return len(s.m)
}
// tomap 转化为普通的map,返回一个线程安全的副本
func (s *syncmap[k, v]) tomap() map[k]v {
s.mu.rlock()
defer s.mu.runlock()
copymap := make(map[k]v, len(s.m))
for k, v := range s.m {
copymap[k] = v
}
return copymap
}
2.2 关键功能说明
线程安全:
- 读操作使用sync.rwmutex的rlock,允许并发读取。
- 写操作使用sync.rwmutex的lock,确保写操作互斥。
支持泛型:
通过k和v泛型参数支持任意键值类型,其中k必须是可比较的。
基本操作:
- load:获取值。
- store:设置值。
- has:判断键是否存在。
- delete:删除键值对。
- range:遍历所有键值对。
- len:获取map的长度。
- tomap:转化为普通map。
3. 使用示例
以下代码演示了syncmap的基本用法:
package main
import (
"fmt"
"syncmap"
)
func main() {
// 创建一个线程安全的map
m := syncmap.newsyncmap[string, int]()
// 添加值
m.store("one", 1)
m.store("two", 2)
// 获取值
if val, ok := m.load("one"); ok {
fmt.println("key 'one':", val)
} else {
fmt.println("key 'one' not found")
}
// 删除值
m.delete("one")
// 遍历所有键值对
m.range(func(key string, value int) bool {
fmt.printf("key: %s, value: %d
", key, value)
return true
})
// 转化为普通map
ordinarymap := m.tomap()
fmt.println("ordinary map:", ordinarymap)
}
运行结果:
key 'one': 1
key: two, value: 2
ordinary map: map[two:2]
4. 总结
自定义线程安全的syncmap具备以下优点:
- 泛型支持:灵活适配不同类型的键值。
- 线程安全:支持高并发场景的安全访问。
- 可扩展性:易于添加更多功能,如合并操作、条件更新等。
通过本文的实现与示例,希望您能更好地理解和应用线程安全map,构建健壮的并发应用。
到此这篇关于go语言如何实现线程安全的map的文章就介绍到这了,更多相关go线程安全map内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论