在日常 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(),并实现基础操作方法,如 Set、Get 和 Delete,来封装对 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 并添加类型检查,我们获得了一个更安全、更健壮的并发映射工具,适合用于缓存、注册表、插件系统等对数据一致性要求较高的场景。