这是关于在 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) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。