Go并发安全需依访问模式选机制:必须用sync.Mutex时是多goroutine读写非原子变量且无法用channel传递所有权;sync.RWMutex仅在读多写少且读耗时明显时更优;channel优先于锁,因通信优于共享内存。

Go语言的并发安全不是靠“加锁就完事”,而是要根据数据访问模式选对机制:用错 sync.Mutex 或盲目上 sync.RWMutex,既掩盖竞态,又拖慢性能。
什么时候必须用 sync.Mutex
当多个 goroutine 同时读写同一个非原子变量(比如 map、struct 字段、切片底层数组),且无法通过 chan 传递所有权时,才需要显式加锁。
- 常见错误现象:
fatal error: concurrent map writes或concurrent map read and map write—— 这说明你正在直接读写一个未加锁的map - 写操作必须独占:哪怕只是
m[key] = value或delete(m, key),都得包在mu.Lock()/mu.Unlock()之间 - 避免锁粒度太粗:不要把整个 HTTP handler 函数塞进
Lock(),只锁真正共享修改的那几行 - 务必用
defer mu.Unlock(),否则中间return或 panic 会导致死锁
sync.RWMutex 真的比 Mutex 快吗
只在「读多写少 + 读操作耗时明显」时才有收益;否则它比 sync.Mutex 开销更大,因为要维护读计数和唤醒逻辑。
- 适用场景:缓存配置项(每秒上千次读,几分钟才更新一次)
- 不适用场景:高频增删的计数器、状态标志位
- 写操作仍需
RWMutex.Lock(),和Mutex一样阻塞所有读写 - 读操作用
RWMutex.RLock(),但若此时有 pending 的写请求,后续新读请求会被阻塞(防止写饥饿) - 忘记配对
RLock()/RUnlock()会导致 goroutine 泄漏,且go run -race很难检测到
为什么优先用 chan 而不是锁
chan 天然承载「通信胜于共享内存」的设计哲学,能把并发逻辑从「保护数据」转向「控制流程」,降低出错概率。
立即学习“go语言免费学习笔记(深入)”;
- 典型适用场景:任务分发、结果收集、信号通知、限流、状态同步
- 用
chan struct{}做信号通知比用bool+Mutex更简洁、无竞态 - 向
chan T发送值即转移所有权,接收方独占该值,无需额外同步 - 避免在
chan上做「先判断再发送」这类非原子操作(如if len(ch) > 0),这本身就是竞态源
sync/atomic 能替代锁吗
只能用于基础类型的原子操作(int32、int64、uint32、uintptr、unsafe.Pointer),且仅限单一字段;不能用于结构体字段组合判断或跨字段逻辑。
- 常见误用:试图用
atomic.LoadInt32和atomic.StoreInt32实现「如果旧值是 A 就设为 B」——这需要atomic.CompareAndSwapInt32,否则仍是竞态 -
atomic对map、slice、string无效,它们底层是结构体,字段间无原子性保证 - 别为了省一行
mu.Lock()强行用atomic模拟复杂逻辑,可读性和正确性会迅速崩坏
最易被忽略的点:很多开发者把「能跑通」当成「并发安全」,但 go run -race 不报错 ≠ 没有竞态,尤其在 RWMutex 漏掉 RUnlock()、chan 关闭后继续发送、或 atomic 误用于复合判断时,问题往往延迟暴露且难以复现。


















