sync.Mutex 不能直接保护 map 类型字段,因为 Go 的内置 map 非并发安全,即使结构体整体加锁,若 map 被外部直接访问或读操作未加锁,仍会触发 concurrent map read and write 错误。
为什么 sync.Mutex 不能直接保护 map 类型字段
Go 的内置 map 非并发安全,即使你用 sync.Mutex 包裹了整个结构体,只要在加锁外访问了 map(比如返回值被外部修改、或方法中漏锁),依然会触发 fatal error: concurrent map read and map write。常见错误是:只锁了写操作,但读操作没锁;或者把 map 字段直接暴露为 public,导致调用方绕过锁直接操作。
实操建议:
- 把
map设为 unexported(小写开头),只提供带锁的Get/Set/Delete方法 - 避免在锁内做耗时操作(如 HTTP 请求、数据库查询),否则会阻塞其他 goroutine
- 如果只是读多写少,考虑用
sync.RWMutex替代,提升并发读性能
sync.RWMutex 的读写锁误用场景
sync.RWMutex 允许多个 goroutine 同时读,但写时会独占锁。问题常出在「读锁未及时释放」或「写锁嵌套读锁」——比如在持有 RWMutex.RLock() 期间调用了另一个也尝试 RLock() 的函数,而该函数又可能升级为写操作,极易死锁。
实操建议:
- 读锁必须配对使用
defer mu.RUnlock(),别依赖作用域自动清理 - 不要在
RLock()持有期间调用不可信的第三方函数,防止隐式锁升级 - 若需「先读后写」逻辑(如检查 key 是否存在再设置),统一用
Lock(),避免先读再写引发竞态
WaitGroup 和 Mutex 混用时的典型陷阱
很多人用 sync.WaitGroup 等待所有 goroutine 结束,同时用 sync.Mutex 保护共享状态,但容易忽略:如果 WaitGroup.Add() 调用位置不对(比如在 goroutine 内部而非启动前),会导致 Wait() 永远不返回;或者在加锁范围内调用 wg.Done(),反而让锁释放延迟,拖慢整体吞吐。