必须将锁与被保护数据绑定在同个结构体中,如safecounter;所有访问均需加锁,禁止不加锁的getter或暴露切片底层数组;lock后立即defer unlock防死锁;读多写少时可用rwmutex但不可嵌套或升级锁。

函数内直接调用 Lock() 和 Unlock() 不够安全
很多初学者会把 sync.Mutex 声明为局部变量,然后在函数里 mu.Lock() → 操作 → mu.Unlock()。这完全无效:局部锁对象每次调用都新建,根本起不到同步作用。真正要保护的是「共享状态」,而锁必须和该状态生命周期一致、作用域可见。
必须把锁和被保护数据绑定在同一个结构体里
这是最常被跳过的一步。比如你要保护一个 counter 整型或一个 items []string 切片,锁不能单独存在,也不能暴露切片本身。正确做法是封装成结构体:
type SafeCounter struct {
mu sync.Mutex
n int
}
func (sc *SafeCounter) Inc() {
sc.mu.Lock()
defer sc.mu.Unlock()
sc.n++
}
- 所有读写
sc.n的方法都必须走Lock()/Unlock()路径 - 不能提供
GetN() int这种不加锁的 getter —— 即使只读,也要加RWMutex.RLock()或统一用Lock() - 如果字段是切片,禁止返回
[]string(会暴露底层数组指针),应返回副本:copy(dst, sc.items)
忘记 defer Unlock() 是死锁高发原因
手动配对 Lock()/Unlock() 容易漏掉,尤其函数有多个 return 分支时。唯一稳妥的方式是紧接 Lock() 后立刻写 defer mu.Unlock():
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
func (q *SafeQueue) Push(f func()) {
q.mu.Lock()
defer q.mu.Unlock() // 即使 panic 或中间 return,这里也一定执行
q.items = append(q.items, f)
}
- 不要在
Lock()前做任何可能 panic 的操作(如空指针解引用) - 避免在锁内调用不可控外部函数(如
http.Get),否则整个临界区会被拖长,阻塞其他 goroutine - 如果真需要锁内调用耗时操作,考虑拆成「先取数据 → 解锁 → 执行 → 再锁更新结果」三段
用 RWMutex 替代 Mutex 的时机很明确
当读多写少(比如配置缓存、白名单列表),且读操作本身不修改状态时,sync.RWMutex 能显著提升并发吞吐。但注意:
-
RLock()和RUnlock()必须严格配对,和Lock()/Unlock()互斥 —— 任意一个写锁持有期间,所有读锁都会阻塞 - 不能嵌套使用:在已持
RLock()的函数里再调Lock()会死锁 - 没有“升级”机制:不能从
RLock()直接转成写锁,必须先RUnlock()再Lock()
真正容易被忽略的是:哪怕只有一次写操作,只要并发读写共存,就必须用锁;而一旦用了锁,就再不能靠“我只读不写”来自我安慰 —— 竞态发生在内存可见性层面,不是靠程序员自觉能绕开的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










