好的,以下是 Golang Mutex 的源代码:
type Mutex struct {
state int32
sema uint32
}
func (m *Mutex) Lock() {
// Fast path: 幸运的情况下,第一次就成功获取锁。
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
return
}
var waitStartTime int64
starving := false
awoke := false
iter := 0
old := m.state
repeat:
for {
// 如果互斥锁目前未被锁定,则尝试获取它。
if old&mutexLocked == 0 {
new := old | mutexLocked // 设置为已加锁状态。
if atomic.CompareAndSwapInt32(&m.state, old, new) {
if starving {
m.sema = 0 // 唤醒任何潜在的阻塞器。
}
return
}
}
var delta int64 = -1
// 如果当前不处于 hysteresis 模式,并且等待持续时间较长,则进入 hysteresis 模式,
// 这样可以避免与其他 goroutine 发生竞争并使得所有 goroutines 都自旋等待.
if !starving && runtime_canSpin(iter) {
iter++
continue
} else if waitStartTime == 0 {
waitStartTime = runtime_nanotime()
} else if !starving && runtime_nanotime()-waitStartTime > starvationThresholdNs && atomic.CompareAndSwapUint32(&m.sema, 0, mutexLocked-1) {
// 如果等待的时间太长,则将锁置于饥饿状态,并创建一个阻塞器。
starving = true
runtime_SemacquireMutex(&m.sema, false, 1)
waitStartTime = runtime_nanotime()
awoke = true
} else { // 如果需要等待,则自旋等待获取锁。
if delta < 0 {
delta = runtime_nanotime()
}
runtime_doSpin()
}
old = m.state
// 如果当前 goroutine 在 hysteresis 模式下(自旋),则重试并稍微增加 spin 的速度。
if old&mutexLocked != 0 && (!starving || (runtime_nanotime()-waitStartTime)&busyWaitInterval == 0) {
iter++
runtime_doSpin()
}
}
}
func (m *Mutex) Unlock() {
if atomic.LoadInt32(&m.state) == 0 {
panic("sync: unlock of unlocked mutex")
}
atomic.StoreInt32(&m.state, 0)
if m.sema > 0 {
runtime_Semrelease(&m.sema, false, 1)
}
}
这里是一些关键点:
- Mutex 是一个结构体,它具有两个字段:state 和 sema。state 表示互斥锁是否被锁定,sema 表示当前有多少个 goroutine 在等待互斥锁。
- Lock() 方法尝试获取互斥锁。如果该互斥锁已经被其他 goroutine 锁定,则它会进入一个 for 循环,自旋等待直到可以获得互斥锁。如果 goroutine 在循环中持续自旋等待的时间过长,它将进入 hysteresis 模式,并阻塞等待互斥锁可用。
- Unlock() 方法用于释放互斥锁。如果该互斥锁未被锁定,则会抛出 panic。如果有其他 goroutine 正在等待这个互斥锁,则它们将被唤醒并尝试获取该互斥锁。
总的来说,Golang Mutex 的实现使用了多种技术(如自旋、hysteresis 等)以优化性能和减少竞争,使其成为一个非常高效且线程安全的同步机制。




