零基础读懂Golang Mutex锁的结构和演化,你也可以写出属于自己的Mutex
2023-08-12 08:20:42
深入解析 Go 语言 Mutex 锁的架构与演进
在 Golang 的并发编程中,Mutex 锁是必不可少的同步工具,用于协调对共享数据的访问,确保一致性和避免数据竞争。本文将深入探讨 Mutex 锁的架构和演进,带你了解其工作原理和历史变迁。
Mutex 锁的架构
Mutex 锁本质上是一个二进制变量(state
),取值只有 0 和 1:
0
表示锁未锁定,任何 Goroutine 都可获取它。1
表示锁已锁定,只有当前持有锁的 Goroutine 可访问共享数据。
此外,Mutex 锁还维护着一个等待队列,存储着尝试获取锁但未成功的 Goroutine。当一个 Goroutine 试图获取锁时,会检查 state
变量:
- 如果
state
为0
,Goroutine 立即获取锁。 - 如果
state
为1
,Goroutine 会被添加到等待队列,并进入休眠状态。
Mutex 锁的演进
Mutex 锁的发展经历了数次迭代和优化。
最初的简单锁
最初的 Mutex 锁十分简陋,仅包含 state
变量。Goroutine 获取锁时,直接检查 state
,获取或报错。
互斥锁
互斥锁是对简单锁的改进,引入了等待队列。当 Goroutine 获取锁失败时,会被添加到等待队列并休眠。当持有锁的 Goroutine 释放锁时,等待队列中的第一个 Goroutine 会自动获取锁。
读写锁
读写锁是一种特殊的 Mutex 锁,允许多个 Goroutine 同时读取共享数据,但仅允许一个 Goroutine 写入数据。它包含三个变量:
state
:取值0
表示未锁定,1
表示读锁,2
表示写锁。- 读锁计数:记录当前持有多个读锁的 Goroutine 数。
- 写锁计数:记录当前持有写锁的 Goroutine 数。
代码示例
以下代码展示了一个简单的 Mutex 锁实现:
type Mutex struct {
state int32
waitQueue chan struct{}
}
func (m *Mutex) Lock() {
for !atomic.CompareAndSwapInt32(&m.state, 0, 1) {
m.waitQueue <- struct{}{}
runtime.Gosched()
}
}
func (m *Mutex) Unlock() {
if atomic.CompareAndSwapInt32(&m.state, 1, 0) {
select {
case <-m.waitQueue:
default:
}
}
}
结论
Mutex 锁是 Golang 中一种重要的并发工具,保障了共享数据的访问一致性。经过不断的演进和优化,Mutex 锁如今已具备丰富的功能和高效的性能,为开发者提供了灵活的并发编程方案。
常见问题解答
-
如何判断 Mutex 锁是否已锁定?
- 检查
state
变量,如果为1
,则已锁定。
- 检查
-
如果一个 Goroutine 持有锁,其他 Goroutine 如何获取它?
- 其他 Goroutine 会被添加到等待队列,等待持有锁的 Goroutine 释放锁。
-
读写锁有什么优势?
- 允许多个 Goroutine 同时读取共享数据,提高并发性能。
-
如何避免死锁?
- 避免在持有锁时尝试再次获取锁或调用可能获取锁的函数。
-
Mutex 锁与其他并发原语有什么区别?
- Mutex 锁专门用于协调对共享数据的访问,而其他并发原语(如管道、通道)用于不同目的。