并发安全Map:Go反射实践

146 阅读2分钟

在日常 Go 开发中,我们常常使用 map 来存储键值对,但原生 map 并不是线程安全的。Go 提供了一个 sync.Map 类型来满足并发场景下的安全访问需求。然而,sync.Map 是类型无关的(即 map[interface{}]interface{}),没有类型检查功能。

这篇文章将介绍如何通过封装 sync.Map,并结合 reflect.Type 实现一个支持类型检查的线程安全映射结构 ConcurrentMap,帮助你在运行时捕获类型错误。


🧱 ConcurrentMap 的结构定义

我们使用 Go 的 reflect.Type 类型记录键和值的类型,在插入时做类型校验:


type ConcurrentMap struct {
	m         sync.Map
	keyType   reflect.Type
	valueType reflect.Type
}

🛠 构造函数与操作方法

我们需要提供构造函数 NewConcurrentMap(),并实现基础操作方法,如 SetGetDelete,来封装对 sync.Map 的访问。


import (
	"fmt"
	"reflect"
	"sync"
)

type ConcurrentMap struct {
	m         sync.Map
	keyType   reflect.Type
	valueType reflect.Type
}

func NewConcurrentMap(keyExample, valueExample interface{}) *ConcurrentMap {
	return &ConcurrentMap{
		keyType:   reflect.TypeOf(keyExample),
		valueType: reflect.TypeOf(valueExample),
	}
}

func (cm *ConcurrentMap) Set(key, value interface{}) error {
	if reflect.TypeOf(key) != cm.keyType {
		return fmt.Errorf("invalid key type: expected %v, got %v", cm.keyType, reflect.TypeOf(key))
	}
	if reflect.TypeOf(value) != cm.valueType {
		return fmt.Errorf("invalid value type: expected %v, got %v", cm.valueType, reflect.TypeOf(value))
	}
	cm.m.Store(key, value)
	return nil
}

func (cm *ConcurrentMap) Get(key interface{}) (interface{}, bool) {
	if reflect.TypeOf(key) != cm.keyType {
		return nil, false
	}
	return cm.m.Load(key)
}

func (cm *ConcurrentMap) Delete(key interface{}) {
	if reflect.TypeOf(key) == cm.keyType {
		cm.m.Delete(key)
	}
}

🧪 使用示例

我们通过 NewConcurrentMap("key", 0) 声明 key 类型为 string,value 类型为 int 的线程安全 map。


func main() {
	// 创建 key 为 string,value 为 int 的 ConcurrentMap
	cm := NewConcurrentMap("example", 0)

	_ = cm.Set("a", 1)
	_ = cm.Set("b", 2)

	if val, ok := cm.Get("a"); ok {
		fmt.Println("Get a:", val) // 输出: Get a: 1
	}

	cm.Delete("a")

	_, ok := cm.Get("a")
	fmt.Println("Still exists?", ok) // 输出: false
}

如果你尝试用错误类型调用 Set(),程序将返回一个错误而不是崩溃,例如:


_ = cm.Set(123, 456) // 错误:invalid key type

✅ 优点

  • 保留了 sync.Map 的线程安全特性;
  • 提供运行时类型检查,避免类型错误;
  • 更适合用在对类型安全有要求的基础库中。

🚀 后续扩展建议

你还可以添加以下功能:

  • 遍历:封装 Range 方法;
  • 类型断言:使用泛型(Go 1.18+)进一步优化体验;
  • 初始化检查:限制只允许某些具体类型的 key/value。

🏁 总结

虽然 Go 现在已经支持泛型,但 reflect.Type 在运行时仍然是处理类型的一大利器。通过封装 sync.Map 并添加类型检查,我们获得了一个更安全、更健壮的并发映射工具,适合用于缓存、注册表、插件系统等对数据一致性要求较高的场景。