必须用sync.mutex当多个goroutine同时读写同一非原子变量(如map、struct字段、slice底层数组)且无法用channel传递所有权时;常见错误是fatal error: concurrent map writes。

什么时候必须加 sync.Mutex?
不是“结构体看起来复杂就要锁”,而是看有没有多个 goroutine 同时读+写同一个非原子字段。比如一个 map[string]int 被两个 goroutine 分别执行 m["a"] = 1 和 val := m["b"],就已触发并发不安全——哪怕读操作本身不改数据,Go 运行时也无法保证 map 内部状态一致性。
常见错误现象:fatal error: concurrent map writes 或偶尔出现的 concurrent map read and map write。这不是偶发 bug,是明确信号:你正在裸奔访问共享 map、slice 底层数组、指针所指内存,或自定义 struct 中含上述类型且被修改。
- 只读场景(如配置加载后不再变更)完全不用锁
-
sync.Map不是通用解药:它适合键固定、读远多于写、且不需要len()或遍历的缓存场景;高频更新或需统计长度时,带锁普通 map 更快更可控 - 别把整个 handler 函数包进
Mutex.Lock()—— 锁粒度太粗会串行化请求,性能反不如不用并发
sync.RWMutex 比 Mutex 快吗?
不一定,甚至多数时候更慢。RWMutex 的优势只在一种组合下成立:读操作耗时明显(比如涉及 IO 或计算),且读频次远高于写(比如 1000:1)。否则它额外维护读计数和唤醒逻辑,开销比 Mutex 大。
写操作永远要调用 RWMutex.Lock(),行为和 Mutex 完全一致:阻塞所有新读、新写;而读操作用 RWMutex.RLock(),但若已有写请求在排队,后续新读也会被阻塞——这是为防写饥饿设计的,不是“读完全不等”。
- 读操作只是取一个
int或string字段?直接用Mutex,别上 RWMutex - 写操作每秒几十次以上?RWMutex 可能导致写饥饿,优先考虑分片锁或 channel 控制写入序列
- 忘记配对
RLock()/RUnlock()不会被go run -race检出,但会导致 goroutine 泄漏,极难排查
为什么 sync.Once 不能保护变量后续读写?
sync.Once 只保证函数体最多执行一次,不提供任何后续访问同步机制。它适合初始化单例、加载配置、打开文件句柄这类“一次性动作”,但初始化完的变量如果被多个 goroutine 读写,仍需额外加锁。
典型误用:var once sync.Once; var config Config; once.Do(func(){ config = loadConfig() }) —— 这只保住了加载过程不重复,config 本身若含可变字段(如 config.Cache = make(map[string]string)),后续并发读写依然危险。
- 初始化后只读?没问题,
Once+ 不可变结构体即可 - 初始化后还要改字段?该上
Mutex就上,Once不负责这部分 - 别试图用
Once替代Mutex或atomic,语义完全不同
channel 真的比锁更安全吗?
不是“更安全”,而是把问题从“保护数据”转向“控制流程”。channel 天然携带所有权转移语义,比如用 chan *Request 把任务分发给 worker goroutine,数据就只在 sender 和 receiver 之间流动,无需共享内存。
但 channel 不是银弹:无缓冲 channel 要求收发双方同时就绪,否则永久阻塞;缓冲 channel 缓冲区满后同样阻塞;所有 channel 操作都应有超时或 default 分支,否则极易卡死。
- 生产者持续写、消费者退出?Channel 未消费导致 goroutine 泄漏
- HTTP 请求中没传
context.Context到http.NewRequestWithContext()?超时无法传播,goroutine 永久挂起 - 用 channel 传递大对象?注意是否发生不必要的内存拷贝,必要时传指针并确保生命周期可控











