这是关于在 Go 中处理并发的系列文章的一部分:
- Go sync.Mutex:普通模式和饥饿模式(本文)
- Go sync.WaitGroup 与对齐问题
- Go sync.Pool 及其背后机制
- Go sync.Cond,最被忽视的同步机制
- Go sync.Map:适合特定工作的正确工具
- Go Sync.Once 很简单……真的吗?
- Go Singleflight 在代码中融化,不在数据库中
Go sync.Mutex:
- 用于确保同一时间只有一个 goroutine 能操作共享资源,如代码、整数、映射、结构体、通道等。
- 有
Lock、Unlock和TryLock三个主要操作,Lock后其他 goroutine 需等待解锁。 - 以简单计数器为例,未加锁时结果不是 1000,是因为存在“竞态条件”,
counter++不是原子操作。 - 使用
atomic包可处理该问题,本文重点介绍sync.Mutex的解决方案,使用mutex.Lock和mutex.Unlock包裹临界区,结果为 1000。 - 设置
GOMAXPROCS为 1 时结果也为 1000,因为 goroutine 不会并行运行。
Mutex 结构:解剖:
sync.Mutex结构包含state(32 位整数)和sema(uint32)两个字段。state字段编码 mutex 的各种状态信息,如锁定、唤醒、饥饿等,sema作为信号量管理等待的 goroutine。
Mutex 锁流程:
mutex.Lock有快速路径(处理未被占用的 mutex)和慢速路径(处理异常情况)。- 快速路径利用 CAS 操作,若失败则进入慢速路径,慢速路径中 goroutine 会自旋尝试获取锁,若失败会增加等待者计数并进入等待队列。
- mutex 有普通模式和饥饿模式,普通模式下等待的 goroutine 按 FIFO 排队竞争,饥饿模式下会直接将锁传递给等待队列头部的 goroutine。
Mutex 解锁流程:
- 解锁流程也有快速路径和慢速路径,快速路径直接清除锁定位,若状态不为零则进入慢速路径。
- 慢速路径根据 mutex 的模式进行不同处理,普通模式下尝试唤醒等待的 goroutine,饥饿模式下直接将锁传递给等待队列头部的 goroutine。
最后介绍了相关文章和 VictoriaMetrics 相关信息,VictoriaMetrics 是一个快速、开源且节省成本的监控服务工具,作者是 Gophers 爱好者。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。