在 Go 中,map 本身并不是线程安全的。如果在并发环境中对 map 进行读写操作,必须采取适当的同步措施来确保线程安全。
使用 sync.RWMutex
sync.RWMutex 提供了读写锁的功能,允许多个读取操作同时进行,但写入操作会独占锁。这种方式非常适合读多写少的场景。
type SafeMap struct { mu sync.RWMutex m map[string]int } func NewSafeMap() *SafeMap { return &SafeMap{ m: make(map[string]int), } } func (sm *SafeMap) Get(key string) (int, bool) { sm.mu.RLock() // 读锁 defer sm.mu.RUnlock() // 确保在函数返回时释放锁 value, exists := sm.m[key] return value, exists } func (sm *SafeMap) Set(key string, value int) { sm.mu.Lock() // 写锁 defer sm.mu.Unlock() // 确保在函数返回时释放锁 sm.m[key] = value } func (sm *SafeMap) Delete(key string) { sm.mu.Lock() // 写锁 defer sm.mu.Unlock() // 确保在函数返回时释放锁 delete(sm.m, key) } func main() { sm := NewSafeMap() sm.Set("apple", 5) value, exists := sm.Get("apple") if exists { fmt.Println("Apple count is:", value) } else { fmt.Println("Apple not found in the map") } }
使用 sync.Map
Go 标准库中的 sync.Map 提供了一种并发安全的 map,它内置了同步机制,适用于需要频繁并发访问的场景。与普通的 map 相比,sync.Map 的一些操作方法有所不同。
func main() { var sm sync.Map // 写入操作 sm.Store("apple", 5) // 读取操作 value, exists := sm.Load("apple") if exists { fmt.Println("Apple count is:", value) } else { fmt.Println("Apple not found in the map") } // 删除操作 sm.Delete("apple") // 再次读取,确认删除 value, exists = sm.Load("apple") if exists { fmt.Println("Apple count is:", value) } else { fmt.Println("Apple not found in the map") } }
比较 sync.RWMutex 和 sync.Map
- sync.RWMutex: 适合读多写少的场景,读操作不会相互阻塞。需要手动实现 map 的读写锁逻辑,更灵活,但也更容易出错。
- sync.Map: 适合读写都频繁的场景,使用简单,内置了并发安全的机制,不需要手动管理锁。但是由于它的内部实现,某些操作可能会比使用普通的 map 加锁的方式慢,特别是在竞争激烈的场景下。
选择哪种方式取决于具体使用场景和性能需求。