必须用sync.mutex当多goroutine同时读写同一变量且含写操作,如count++、map增删、结构体字段更新;零值可用但禁止复制,lock后须立即defer unlock以保安全。

给函数加 sync.Mutex 不等于“防重入”,它只保临界区互斥执行,不控制调用次数;真正要防的是共享状态被并发写乱,而不是函数被调用了几次。
sync.Mutex 什么时候必须用?
只要多个 goroutine 同时读写同一变量,且至少有一个是写操作,就必须加锁。这不是性能优化,是避免数据错乱的底线。
-
count++—— 底层是读→改→写三步,非原子,必竞态 - 结构体字段更新(如
user.Balance += amount) - 对
map或切片做增删改(哪怕只是map[key] = value) - 状态标志切换(如
isRunning = true配合后台 goroutine 控制)
注意:sync.Mutex 零值可用,但绝不能复制传参——传的是副本,原锁完全失效;必须作为结构体字段、用指针接收者访问。
Lock/Unlock 怎么配对才安全?
最常见死锁/锁泄露,都出在 Unlock() 没被执行。唯一靠谱做法:defer mu.Unlock() 必须紧跟在 mu.Lock() 后面,哪怕中间有 error 判断或提前 return。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 错误写法:
mu.Lock()放开头,defer mu.Unlock()放函数末尾 → panic 或 early return 时锁不释放 - 正确写法:
mu.Lock()后立刻defer mu.Unlock(),之后再做任何逻辑 - 禁止在锁内调用可能 panic 的函数(如
json.Marshal、未判空解引用),否则defer不触发,锁永久卡住
示例:
func (a *Account) Deposit(amount int) {
a.mu.Lock()
defer a.mu.Unlock() // ✅ 紧跟 Lock,覆盖所有退出路径
a.balance += amount
}
为什么 RWMutex 有时反而更危险?
RWMutex 的读锁(RLock())只防读-写冲突,不防写-写竞争;很多人误用它做“先查后写”,结果引入竞态窗口。
- 典型错误模式:
rw.RLock()→ 检查 key 是否存在 →rw.RUnlock()→mu.Lock()→ 写入 - 问题:两个 goroutine 可同时通过读检查,然后都进写临界区,导致重复写或覆盖
- 正确做法:所有依赖状态判断 + 修改的操作,必须包在同一个
mu.Lock()/mu.Unlock()里 - 读多写少场景可考虑
sync.Map,但别为了“看起来快”而牺牲正确性
锁粒度太大,性能崩坏但没报错
锁本身不报错,但把耗时操作塞进临界区,会让其他 goroutine 长时间排队,吞吐暴跌——这不是 bug,是设计缺陷。
- 禁止在锁内做网络请求、文件 I/O、长循环、复杂计算
- 正确做法:先算好要写的数据(比如拼好 JSON 字符串),再加锁更新共享状态
- 若状态变更逻辑复杂,不如用 channel + 单个 goroutine 串行处理,彻底避开锁竞争
- 真正难的不是加锁,而是界定“什么才算一次完整的状态变更”——这个边界划错了,锁再多也没用
临界区的边界模糊、状态判断和修改拆开、defer Unlock() 放错位置——这三类问题,在高并发服务里往往拖到压测才暴露,而且极难复现。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










