Go语言如何实现线程安全的Map

 更新时间:2024年11月26日 09:09:30   作者:熬了夜的程序员  
Go语言内置的map虽然高效,但并不是线程安全的,若在多线程环境中直接操作map,可能会引发并发写入的错误,下面我们就来看看如何实现线程安全的Map吧

在并发编程中,数据共享和访问是一个重要的主题。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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入Golang中的sync.Pool详解

    深入Golang中的sync.Pool详解

    这篇文章主要介绍了深入Golang中的sync.Pool详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • go mod tidy加载模块超时的问题及解决

    go mod tidy加载模块超时的问题及解决

    go mod tidy加载模块超时的问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • 浅谈golang结构体偷懒初始化

    浅谈golang结构体偷懒初始化

    这篇文章主要介绍了浅谈golang结构体偷懒初始化,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Golang实现AES对称加密算法实例详解

    Golang实现AES对称加密算法实例详解

    所谓对称加密是指在加密和解码时使用同一密钥的加密方式,下面这篇文章主要给大家介绍了关于Golang实现AES对称加密算法的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-02-02
  • Golang RSA生成密钥、加密、解密、签名与验签的实现

    Golang RSA生成密钥、加密、解密、签名与验签的实现

    RSA 是最常用的非对称加密算法,本文主要介绍了Golang RSA生成密钥、加密、解密、签名与验签的实现,具有一定的参考价值,感兴趣的可以了解一下
    2023-11-11
  • Go 类型转换工具包strconv包的用法

    Go 类型转换工具包strconv包的用法

    Go 语言的 strconv 包提供了用于基本数据类型之间转换的函数,本文主要介绍了Go 类型转换工具包strconv包的用法,具有一定的参考价值,感兴趣的可以了解一下
    2024-05-05
  • 详解Golang开启http服务的三种方式

    详解Golang开启http服务的三种方式

    这篇文章主要介绍了详解Golang开启http服务的三种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 详解Golang中哪些类型可以作为map的key

    详解Golang中哪些类型可以作为map的key

    在 Go 语言中,map 是一种内置的关联数据结构类型,由一组无序的键值对组成,每个键都是唯一的,并与一个对应的值相关联,本文将详细介绍哪些类型的变量可以作为 map 的键,并通过实例进行说明,感兴趣的朋友可以参考下
    2024-01-01
  • GoLang编程必备:GoFrame GoLand插件介绍

    GoLang编程必备:GoFrame GoLand插件介绍

    掌握GoLang编程必备工具,我们来深入了解GoFrame GoLand插件,这个小巧但强大的插件将极大提升你的开发效率,让代码流畅如行云流水,一起跟随本指南,解锁更智能的编程之旅!
    2023-12-12
  • Go语言中的Base64编码原理介绍以及使用

    Go语言中的Base64编码原理介绍以及使用

    Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,可用于在HTTP环境下传递较长的标识信息,下面这篇文章主要给大家介绍了关于Go语言中的Base64编码原理介绍以及使用的相关资料,需要的朋友可以参考下
    2022-01-01

最新评论