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)
	}
}
相关推荐
利威尔xu8 分钟前
Go IM 源码实战(11):token 只是个 HASH,多端互踢的顺序错了就踢错人
后端·微服务·面试·架构·golang
猿究院--Cu-Sn合金31 分钟前
Spring Boot + DeepSeek 求职 Agent 实践:工具注册、多轮调用与简历 JD 对比
数据库·人工智能·spring boot·后端·deepseek
程序员cxuan32 分钟前
别学 subagent 了,很可能要凉了。
后端·agent·ai编程
小咕聊编程38 分钟前
基于Django的个人摄影博客网站设计与开发
后端·python·django
未秃头的程序猿1 小时前
@ConditionalOnMissingBean 把我配好的 RabbitMQ 手动 ack 吃掉了
后端·rabbitmq
回家路上绕了弯1 小时前
当 AI 成为队友:团队协作使用 AI 的实践与思考
后端
陈随易1 小时前
副业搞钱点子之独立开发
前端·后端·程序员
MetaLite2 小时前
Spring Boot 加了 @Valid,嵌套参数为什么还没校验?
java·spring boot·后端
霸道流氓气质2 小时前
Lingua 语言检测库:n-gram 统计模型、置信度阈值调优与 Spring Boot 分层检测管道的生产级实战
java·spring boot·后端
geovindu2 小时前
rust: Read-Write Lock Pattern
开发语言·后端·设计模式·rust·读写锁模式·同步型模式