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 TCP粘包拆包问题的解决方法

    Golang TCP粘包拆包问题的解决方法

    这篇文章主要给大家介绍了Golang TCP粘包拆包问题的解决方法,文中通过示例代码介绍的非常详细,对大家学习或者使用Golang具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-07-07
  • Golang Map value不可寻址使用指针类型代替示例详解

    Golang Map value不可寻址使用指针类型代替示例详解

    这篇文章主要为大家介绍了Golang Map value不可寻址使用指针类型代替示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • go logger不侵入业务代码使用slog替换zap并实现callerSkip详解

    go logger不侵入业务代码使用slog替换zap并实现callerSkip详解

    这篇文章主要为大家介绍了go logger不侵入业务代码使用slog替换zap并实现callerSkip详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Go语言中的定时器原理与实战应用

    Go语言中的定时器原理与实战应用

    在Go语言中,Timer和Ticker是处理定时任务的重要工具,Timer用于一次性事件,而Ticker则用于周期性事件,本文详细介绍了这两种定时器的创建、使用和停止方法,并通过实际案例展示了它们在监控日志、检查系统状态等方面的应用
    2024-10-10
  • Go 语言入门学习之正则表达式

    Go 语言入门学习之正则表达式

    这篇文章主要介绍了Go 语言入门学习之正则表达式,文章基于GO语言的相关资料展开详细内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-04-04
  • 使用go语言解析xml的实现方法(必看篇)

    使用go语言解析xml的实现方法(必看篇)

    下面小编就为大家带来一篇使用go语言解析xml的实现方法(必看篇)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Golang中自定义json序列化时间格式的示例代码

    Golang中自定义json序列化时间格式的示例代码

    Go语言作为一个由Google开发,号称互联网的C语言的语言,自然也对JSON格式支持很好,下面这篇文章主要介绍了关于Golang中自定义json序列化时间格式的相关内容,下面话不多说了,来一起看看详细的介绍吧
    2024-08-08
  • go语言实现聊天服务器的示例代码

    go语言实现聊天服务器的示例代码

    这篇文章主要介绍了go语言实现聊天服务器的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • 如何让shell终端和goland控制台输出彩色的文字

    如何让shell终端和goland控制台输出彩色的文字

    这篇文章主要介绍了如何让shell终端和goland控制台输出彩色的文字的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • 在Go中创建随机的安全密码

    在Go中创建随机的安全密码

    今天小编就为大家分享一篇关于在Go中创建随机的安全密码,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-10-10

最新评论