必须用 sync.Mutex 当多 goroutine 同时读写非原子变量且无法用 channel 传递所有权;只读可不加锁;RWMutex 仅在读多写少且读耗时明显时更优;channel 优先于锁,因通信优于共享内存。

Go 语言的并发安全不是靠“加锁就完事”,而是要根据数据访问模式选择最轻量、最匹配的机制;用错 sync.Mutex 或盲目上 sync.RWMutex 反而会掩盖竞争、拖慢性能。
什么时候必须用 sync.Mutex?
当多个 goroutine 同时读写同一个变量(尤其是非原子类型,比如 map、struct 字段、切片底层数组),且无法通过 channel 传递所有权时,才需要显式加锁。
常见错误现象:fatal error: concurrent map writes 或 concurrent map read and map write —— 这说明你正在直接读写一个未加锁的 map。
- 只读场景不用锁:如果所有 goroutine 都只读,且该
map在启动后不再修改,可放心并发读 -
map写操作必须独占:哪怕只是m[key] = value或delete(m, key),都需在Mutex.Lock()/Unlock()之间 - 避免锁粒度太粗:不要把整个 HTTP handler 函数包进
Lock(),应只锁真正共享修改的那几行
sync.RWMutex 真的比 Mutex 快吗?
只在「读多写少 + 读操作耗时明显」时才有收益;否则它比 Mutex 开销更大,因为要维护读计数和唤醒逻辑。
立即学习“go语言免费学习笔记(深入)”;
使用场景举例:缓存配置项(每秒上千次读,几分钟才更新一次);不适用场景:高频增删的计数器、状态标志位。
- 写操作仍需
RWMutex.Lock(),和Mutex一样阻塞所有读写 - 读操作用
RWMutex.RLock(),但若此时有 pending 的写请求,后续新读请求会被阻塞(防止写饥饿) - 忘记配对
RLock()/RUnlock()会导致 goroutine 泄漏,且不易被go run -race检测到
为什么推荐优先用 channel 而不是锁?
channel 天然承载「通信胜于共享内存」的设计哲学,能把并发逻辑从「保护数据」转向「控制流程」,降低出错概率。
典型适用场景:任务分发、结果收集、信号通知、限流、状态同步。
- 用
chan struct{}做信号通知比用bool+Mutex更简洁、无竞态 - 向
chan T发送值即转移所有权,接收方独占该值,无需额外同步 - 避免在 channel 上做「先判断再发送」这类非原子操作(如
if len(ch) ),这本身就是竞态源
atomic 包能替代锁吗?
只能用于基础类型的原子操作(int32、int64、uint32、uintptr、unsafe.Pointer),且仅限单一字段;不能用于结构体字段组合判断或跨字段逻辑。
常见误用:试图用 atomic.LoadInt32 和 atomic.StoreInt32 实现「如果旧值是 A 就设为 B」——这需要 atomic.CompareAndSwapInt32,否则仍是竞态。
-
atomic.Value可安全存取任意类型指针(如*Config),适合替换整个只读配置对象 -
atomic.AddInt64适合计数器,但注意它返回新值,不是旧值 - 别对
struct{ a, b int }的字段单独用 atomic——结构体本身不可原子更新,要用atomic.Value包一层
真正难的不是选哪个工具,而是识别「谁在何时以何种方式访问什么数据」;-race 能帮你发现已发生的竞态,但设计阶段就要想清楚数据生命周期和所有权边界。



















