使用Go语言实现线程安全的Map

目录

  • 使用Go语言实现线程安全的Map
    • [1. 为什么需要线程安全的Map?](#1. 为什么需要线程安全的Map?)
    • [2. 如何实现线程安全的Map?](#2. 如何实现线程安全的Map?)
      • [2.1 基本实现](#2.1 基本实现)
      • [2.2 关键功能说明](#2.2 关键功能说明)
    • [3. 使用示例](#3. 使用示例)
    • [4. 总结](#4. 总结)
    • 参考链接

使用Go语言实现线程安全的Map

在并发编程中,数据共享和访问是一个重要的主题。Go语言内置的map虽然高效,但并不是线程安全的。若在多线程环境中直接操作map,可能会引发并发写入的错误(fatal error: concurrent map writes)。因此,在需要并发访问map时,必须采取措施确保线程安全。

本文将介绍如何使用Go语言的泛型和sync.RWMutex实现一个线程安全的Map,同时支持常见的操作,例如增删改查、遍历和转化为普通的Map。


1. 为什么需要线程安全的Map?

Go语言内置的map在多线程环境中并不安全。例如,以下代码可能引发崩溃:

go 复制代码
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的完整实现:

go 复制代码
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.RWMutexRLock,允许并发读取。
    • 写操作使用sync.RWMutexLock,确保写操作互斥。
  • 支持泛型

    • 通过KV泛型参数支持任意键值类型,其中K必须是可比较的。
  • 基本操作

    • Load:获取值。
    • Store:设置值。
    • Has:判断键是否存在。
    • Delete:删除键值对。
    • Range:遍历所有键值对。
    • Len:获取map的长度。
    • ToMap:转化为普通map

3. 使用示例

以下代码演示了SyncMap的基本用法:

go 复制代码
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具备以下优点:

  1. 泛型支持:灵活适配不同类型的键值。
  2. 线程安全:支持高并发场景的安全访问。
  3. 可扩展性:易于添加更多功能,如合并操作、条件更新等。

通过本文的实现与示例,希望您能更好地理解和应用线程安全Map,构建健壮的并发应用。


参考链接

相关推荐
花酒锄作田15 小时前
Go - Zerolog使用入门
golang
用户962377954485 天前
VulnHub DC-3 靶机渗透测试笔记
安全
叶落阁主6 天前
Tailscale 完全指南:从入门到私有 DERP 部署
运维·安全·远程工作
用户962377954488 天前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机8 天前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机8 天前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
用户962377954488 天前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star8 天前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
用户962377954488 天前
DVWA Weak Session IDs High 的 Cookie dvwaSession 为什么刷新不出来?
安全
cipher10 天前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全