sync.mutex必须在读写共享变量前加锁,否则-race会直接报data race错误;锁保护的是变量本身而非名称,结构体应嵌入mutex并用指针接收者;lock/unlock必须成对且不跨函数,defer unlock需紧跟lock;map、slice等引用类型仍需显式加锁。

sync.Mutex 必须在读写共享变量前加锁,否则 go run -race 会立刻报错
不加锁就并发读写,data race 不是“可能出问题”,而是“必然触发”。Go 的竞态检测器会在运行时直接打印类似 Read at 0x00c000010240 by goroutine 7 的错误。这不是警告,是明确告诉你:这里正在破坏内存一致性。
-
count++看似一行,实则是读+修改+写回三步,任何一步被其他 goroutine 插入都会丢数据 - 哪怕只有一处写、多处读,也必须加锁——
sync.RWMutex是优化手段,不是替代方案 - 锁保护的是「变量本身」,不是「变量名」;
var m map[string]int加了锁,但没锁住m["k"] = v这个操作,照样 race
结构体里嵌入 sync.Mutex 最安全,别传指针或复制值
sync.Mutex 是值类型,零值可用,但复制后互不相关——两个 goroutine 分别对副本加锁,等于没锁。常见翻车点是把含 mu sync.Mutex 的结构体按值传递,或者用指针却忘了同步访问路径。
- 正确做法:结构体内直接嵌入
mu sync.Mutex,所有方法通过*T接收者调用,确保锁实例唯一 - 错误示例:
func (c container) inc()—— 值接收者导致每次调用都复制整个结构体,包括mu - 别手动初始化
mu,它的零值就是未锁定状态;var mu sync.Mutex直接可用
defer mu.Unlock() 必须紧跟 mu.Lock(),且不能跨函数配对
忘记 Unlock() 会导致死锁;重复 Unlock() 会 panic:sync: unlock of unlocked mutex。这不是边界情况,是代码路径稍有分支就踩中的坑。
- 唯一稳妥写法:
mu.Lock(); defer mu.Unlock(),放在函数开头,覆盖所有 return/panic 路径 - 禁止在一个函数里
Lock(),在另一个函数里Unlock()—— 锁的生命周期必须封闭在单次调用内 - if/else 分支多?没关系,
defer在函数退出时才执行,不会因提前 return 被跳过
map 并发读写必须整体加锁,sync.RWMutex 只在读远多于写时才值得换
内置 map 本身不是线程安全的,哪怕只是并发 read 和 write,也会 crash。很多人以为“读操作不用锁”,其实是错觉——写操作会重排底层哈希桶,读时可能访问到半更新状态。
- 简单场景直接用
sync.Mutex:写操作少、读写频率接近,代码更直白,不易误用 - 真要换
sync.RWMutex,注意RLock()和Lock()互斥:一个 goroutine 持有RLock()时,Lock()会一直阻塞,直到所有读锁释放 - 切片、channel 同理:它们是引用类型,但底层数组/队列仍需显式保护,不能靠“类型安全”蒙混过关
mu.Lock() / defer mu.Unlock(),再根据 profile 数据逐步放开。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











