sync.Mutex源码

sync.Mutex

go 复制代码
type Mutex struct {
	_ noCopy

	mu isync.Mutex
}

// isync "internal/sync"
type Mutex struct {
	state int32 // 一个 int32 里用位表示「是否加锁、是否唤醒中、是否饥饿、等待者个数」
	sema  uint32 // 信号量,用来在抢不到锁时 sleep / 被 Unlock 时 wake 一个 waiter。
}

const (
	mutexLocked      = 1 << 0   // 1:当前有人持有锁
	mutexWoken       = 1 << 1   // 2:已有 goroutine 被唤醒(或正在自旋),Unlock 不必再唤醒别人
	mutexStarving    = 1 << 2   // 4:饥饿模式
	mutexWaiterShift = 3        // 高 29 位:等待者数量
)

Lock

  1. 快速路径:当前 state == 0(没人持锁、没人等),CAS 成 mutexLocked 就拿到锁,直接 return。
  2. 否则lockSlow(),里面做自旋、排队、饥饿切换等。
go 复制代码
func (m *Mutex) Lock() {
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
		...
		return
	}
	m.lockSlow()
}
go 复制代码
func (m *Mutex) lockSlow() {
	...
	for {
		// 当前有人持锁且不是饥饿模式,开始自旋
		if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
			if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
				atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
				//别去叫醒队列里的兄弟了,我已经在这等着抢了
				awoke = true
			}
			runtime_doSpin()
			iter++
			old = m.state
			continue
		}
		new := old
		if old&mutexStarving == 0 {
			new |= mutexLocked // 只要不是饥饿模式,就尝试抢锁
		}
		if old&(mutexLocked|mutexStarving) != 0 {
			new += 1 << mutexWaiterShift // 如果锁被占或在饥饿模式,自己乖乖去排队,等待者+1
		}
		if starving && old&mutexLocked != 0 {
			new |= mutexStarving // 如果我已经等了超过 1ms,就把锁标记为饥饿模式
		}
		...
		if atomic.CompareAndSwapInt32(&m.state, old, new) {
			if old&(mutexLocked|mutexStarving) == 0 {
				break // 成功抢到锁,大功告成
			}
			// 抢不到,调用信号量进入休眠
			queueLifo := waitStartTime != 0
			if waitStartTime == 0 {
				waitStartTime = runtime_nanotime()
			}
			runtime_SemacquireMutex(&m.sema, queueLifo, 2)
			// 被唤醒后,检查自己等了多久,如果太久,开启饥饿标记
			starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
			old = m.state
			// 饥饿模式的处理
			if old&mutexStarving != 0 {
				// 此时所有权直接从上一个 Unlock 的人手里传到了这个被唤醒的人手里
				...
				delta := int32(mutexLocked - 1<<mutexWaiterShift)
				if !starving || old>>mutexWaiterShift == 1 {
					// 如果我是最后一个人,或者我等的没那么久了,退出饥饿模式
					delta -= mutexStarving
				}
				atomic.AddInt32(&m.state, delta)
				break
			}
			awoke = true
			iter = 0
		} else {
			old = m.state
		}
	}

	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
}

TryLock

  1. 若已经加锁 或处于饥饿(饥饿时锁要交给 waiter,不能给 TryLock),直接返回 false。
  2. 否则 CAS 把 mutexLocked 置 1,成功返回 true,失败返回 false。不排队、不自旋
go 复制代码
func (m *Mutex) TryLock() bool {
	old := m.state
	if old&(mutexLocked|mutexStarving) != 0 {
		return false
	}

	if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
		return false
	}

	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
	return true
}

UnLock

go 复制代码
func (m *Mutex) Unlock() {
	if race.Enabled {
		_ = m.state
		race.Release(unsafe.Pointer(m))
	}

	// Fast path: drop lock bit.
	new := atomic.AddInt32(&m.state, -mutexLocked)
	if new != 0 {
		// Outlined slow path to allow inlining the fast path.
		// To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.
		m.unlockSlow(new)
	}
}
  • 检查 :若 (new+mutexLocked)&mutexLocked == 0,说明本来就没锁,fatal("unlock of unlocked mutex")
  • 非饥饿
    • 若没有 waiter,或已经有别人把锁/唤醒拿走了,直接 return。
    • 否则 CAS:waiter 数减 1、置上 mutexWoken ,成功则 runtime_Semrelease(&m.sema, ...) 唤醒一个 waiter。
  • 饥饿
    • 不抢锁,直接把锁交给队头 waiter
go 复制代码
func (m *Mutex) unlockSlow(new int32) {
	if (new+mutexLocked)&mutexLocked == 0 {
		fatal("sync: unlock of unlocked mutex")
	}
	if new&mutexStarving == 0 {
		old := new
		for {
			if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
				return
			}
			// Grab the right to wake someone.
			new = (old - 1<<mutexWaiterShift) | mutexWoken
			if atomic.CompareAndSwapInt32(&m.state, old, new) {
				runtime_Semrelease(&m.sema, false, 2)
				return
			}
			old = m.state
		}
	} else {
		runtime_Semrelease(&m.sema, true, 2)
	}
}
相关推荐
计算机学姐2 小时前
基于SpringBoot的校园二手书籍交易系统【个性化推荐+数据可视化统计+我买到的+我卖出的】
vue.js·spring boot·后端·mysql·信息可视化·intellij-idea·mybatis
神奇小汤圆2 小时前
JDK17 前后写法对比:差点没认出是 Java!
后端
偷懒下载原神2 小时前
【linux操作系统】信号
linux·运维·服务器·开发语言·c++·git·后端
SmartBrain2 小时前
Spring Boot 中常用注解总结(AI工程化)
java·人工智能·spring boot·后端
小江的记录本2 小时前
【Redis】Redis常用命令速查表(完整版)
java·前端·数据库·redis·后端·spring·缓存
AMoon丶2 小时前
Golang--垃圾回收
java·linux·开发语言·jvm·后端·算法·golang
Densen20142 小时前
企业H5站点升级PWA (二)
java·后端·spring
用户2190326527352 小时前
部署OpenClaw整合QQ机器人
后端
后端不背锅2 小时前
服务网格 Istio:微服务架构的下一步
后端