sync.Mutex零值有效,无需初始化;但必须用指针接收者、禁止复制、所有读写均需锁保护,否则仍会竞态。

加了 sync.Mutex 还出现数据不一致?大概率不是锁没用,而是锁没“管住”所有访问路径。
为什么 mu.Lock() 写了,竞态还是被 go run -race 报出来?
最常见原因:只锁了写,没锁读;或者结构体用值传递,导致每次操作的其实是不同 sync.Mutex 实例。
- 所有对共享字段的读、写操作,都必须包裹在
mu.Lock()和mu.Unlock()之间——包括fmt.Println(c.count)这种看似“只读”的语句 - 如果
sync.Mutex是嵌入结构体的字段(比如type Counter struct { mu sync.Mutex; count int }),调用方必须传*Counter指针,否则方法内c.mu.Lock()锁的是副本,原结构体的锁始终没被触碰 - 切忌在方法接收者声明中写
func (c Counter) Inc()(值接收者),应改为func (c *Counter) Inc()(指针接收者)
sync.RWMutex 比 sync.Mutex 快,但什么时候不该换?
读多写少时换 RWMutex 能提升吞吐,但别为了“听起来高级”而盲目替换。
- 如果读操作本身极轻(如读一个
int或bool),sync.Mutex的开销更小,且逻辑更直白 -
RWMutex.RLock()和RUnlock()必须严格成对;漏掉一次RUnlock(),后续所有Lock()都会永久阻塞 - 大量并发读 + 偶尔写时,可能出现“写饥饿”:新来的写请求一直等不到所有读锁释放,尤其当读操作耗时较长时
用 defer mu.Unlock() 真的安全吗?
在单一入口、无提前 return 的简单函数里是安全的;但稍复杂就容易翻车。
立即学习“go语言免费学习笔记(深入)”;
- 绝不把
defer mu.Unlock()放在if分支内部——分支未执行,锁就永远不释放 - 函数中有多个 return 路径时,
defer只在函数末尾触发,但若中间 panic 或 os.Exit(),它可能根本没机会运行 - 更稳妥的写法是:紧接
mu.Lock()后立刻写defer mu.Unlock(),且确保该函数逻辑线性、无异常跳转
为什么 sync.Mutex{} 不初始化也能用?
因为 sync.Mutex 的零值就是有效、未锁定的状态。这是 Go 官方明确保证的行为。
- 直接声明
var mu sync.Mutex或mu := sync.Mutex{}都合法,无需new(sync.Mutex) - 但一旦使用过,就不能再复制该变量(比如赋值给另一个
sync.Mutex变量或作为参数值传递),否则会触发go vet报告 “copy of unlocked mutex” - 如果你看到代码里写了
mu := &sync.Mutex{},那是冗余的——sync.Mutex本身不含指针或大字段,没必要取地址
真正难的从来不是“怎么加锁”,而是判断“哪里需要加锁”以及“锁的生命周期是否覆盖全部访问”。哪怕只漏掉一行裸读,-race 就会立刻打脸。


















