Golang中sync/mutex.go源码问题请教

Golang中sync/mutex.go源码问题请教 我一直在学习 Go 语言的源代码。我对 sync/mutex.go 文件中的第 164 行有一个疑问:

image

为什么不使用 atomic.LoadInt32,就像 sync/map.go 中的第 381 行那样:

func (e *entry) tryExpungeLocked() (isExpunged bool) {
	p := atomic.LoadPointer(&e.p)
	for p == nil {
		if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
			return true
		}
		p = atomic.LoadPointer(&e.p) // line 381
	}
	return p == expunged
}

在这种情况下(sync/mutex.go 的第 164 行)不存在并发问题吗?


更多关于Golang中sync/mutex.go源码问题请教的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中sync/mutex.go源码问题请教的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


sync/mutex.go 第164行,代码直接读取 m.state 而不使用 atomic.LoadInt32 是安全的,因为这段代码在互斥锁已经锁定的上下文中执行。让我们分析具体代码:

func (m *Mutex) lockSlow() {
    // ...
    for {
        if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
            // 自旋逻辑
            continue
        }
        new := old
        if old&mutexStarving == 0 {
            new |= mutexLocked
        }
        if atomic.CompareAndSwapInt32(&m.state, old, new) {
            // 第164行附近:
            if old&(mutexLocked|mutexStarving) == 0 {
                break // 成功获取锁
            }
            // ...
        }
        old = m.state // 第164行:直接读取
    }
}

关键点:

  1. 锁保护lockSlow() 方法在调用时,调用者已经通过 atomic.CompareAndSwapInt32 尝试获取锁,整个循环在竞争修改 m.state
  2. 内存顺序保证atomic.CompareAndSwapInt32 提供了内存屏障,确保之前的写入对其他goroutine可见
  3. 读取时机:第164行的 old = m.state 发生在 CAS 失败后,此时需要获取最新的状态值重新尝试

虽然存在并发访问,但这里的直接读取是安全的,因为:

  • 如果当前goroutine的 CAS 成功,它已经获得了锁或锁的状态变更权
  • 如果 CAS 失败,说明其他goroutine修改了状态,此时读取最新值是必要的
  • Go的内存模型保证了对 int32 的字对齐读取是原子的

示例证明这种模式的安全性:

func (m *Mutex) lockSlow() {
    var old int32
    for {
        // 在CAS失败后,我们需要获取最新的state值
        // 直接读取是安全的,因为:
        // 1. state是int32,字对齐读取是原子的
        // 2. 我们刚刚的CAS操作建立了happens-before关系
        old = m.state
        
        // 基于old值计算new值
        new := old | mutexLocked
        
        // CAS操作本身是原子的,并创建内存屏障
        if atomic.CompareAndSwapInt32(&m.state, old, new) {
            if old&mutexLocked == 0 {
                break // 成功获取锁
            }
            // 锁已被占用,继续循环
        }
        // CAS失败,循环继续,old会被重新读取
    }
}

相比之下,sync/map.go 第381行使用 atomic.LoadPointer 是因为:

  1. 操作的是指针类型,不是基本类型
  2. 需要确保在32位系统上也能原子读取64位值
  3. sync.Map 的并发模式不同,没有类似的锁保护上下文

mutex.go 的这个特定位置,直接读取 m.state 是经过深思熟虑的设计选择,既保证了正确性,又避免了不必要的原子操作开销。

回到顶部