同步锁
概念说明
sync.Mutex 是互斥锁。
同一时间只允许一个 goroutine 进入被锁保护的代码区域。
这段被保护的代码区域通常叫临界区。
临界区里一般会读写共享变量、map、切片等数据。
语法/规则
- 使用
mu.Lock()加锁。 - 使用
mu.Unlock()解锁。 - 推荐在加锁后立即写
defer mu.Unlock(),避免提前返回忘记解锁。 - 锁保护的是
Lock()到Unlock()之间这段共享数据操作,不是整个 goroutine。 - 不要复制已经使用过的
sync.Mutex。
场景示例:两个 goroutine 都要修改同一个计数器
| |
输出结果(为了便于观察,下面是一种常见结果):
| |
锁保护的范围在哪里
看 writeFromA 里的这几行:
| |
锁保护的就是这段范围:
| |
也就是说:
fmt.Println("A: want lock")不在锁保护范围内。current := *count和*count = current + 1在锁保护范围内。writeFromB执行到mu.Lock()时,如果 A 还没解锁,就只能等待。- 不是“锁住了 A 这个 goroutine”,而是“谁拿到同一把锁,谁才能进入这段共享数据操作”。
这个示例体现了什么
writeFromA和writeFromB是两个不同函数,它们都在修改同一个count。- 没有锁时,它们可能同时读写
count,从而产生数据竞争。 - 加锁后,同一时间只有一个 goroutine 能进入
count的读写区域。 - 这就是
Mutex的独特作用:让共享数据的修改变成“一个一个来”。
常见错误
- 加锁后忘记解锁,导致其他 goroutine 永久阻塞。
- 锁的范围过大,把耗时 IO 也放在锁内,降低并发性能。
- 在不同地方用不同锁保护同一份数据,导致保护失效。
- 复制带锁的结构体,可能造成锁状态混乱。