返回

零基础读懂Golang Mutex锁的结构和演化,你也可以写出属于自己的Mutex

后端

深入解析 Go 语言 Mutex 锁的架构与演进

在 Golang 的并发编程中,Mutex 锁是必不可少的同步工具,用于协调对共享数据的访问,确保一致性和避免数据竞争。本文将深入探讨 Mutex 锁的架构和演进,带你了解其工作原理和历史变迁。

Mutex 锁的架构

Mutex 锁本质上是一个二进制变量(state),取值只有 0 和 1:

  • 0 表示锁未锁定,任何 Goroutine 都可获取它。
  • 1 表示锁已锁定,只有当前持有锁的 Goroutine 可访问共享数据。

此外,Mutex 锁还维护着一个等待队列,存储着尝试获取锁但未成功的 Goroutine。当一个 Goroutine 试图获取锁时,会检查 state 变量:

  • 如果 state0,Goroutine 立即获取锁。
  • 如果 state1,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 锁如今已具备丰富的功能和高效的性能,为开发者提供了灵活的并发编程方案。

常见问题解答

  1. 如何判断 Mutex 锁是否已锁定?

    • 检查 state 变量,如果为 1,则已锁定。
  2. 如果一个 Goroutine 持有锁,其他 Goroutine 如何获取它?

    • 其他 Goroutine 会被添加到等待队列,等待持有锁的 Goroutine 释放锁。
  3. 读写锁有什么优势?

    • 允许多个 Goroutine 同时读取共享数据,提高并发性能。
  4. 如何避免死锁?

    • 避免在持有锁时尝试再次获取锁或调用可能获取锁的函数。
  5. Mutex 锁与其他并发原语有什么区别?

    • Mutex 锁专门用于协调对共享数据的访问,而其他并发原语(如管道、通道)用于不同目的。