go 源码解读 - sync.Mutex

news2025/1/19 11:21:03

sync.Mutex

    • mutex简介
    • mutex 方法
    • 源码
      • 标志位
      • 获取锁
      • Lock
      • lockSlow
      • Unlock
      • 怎么 调度 goroutine
      • runtime 方法

mutex简介

mutex 是 一种实现互斥的同步原语。(go-version 1.21) (还涉及到Go运行时的内部机制)

mutex 方法

  • Lock() 方法用于获取锁,如果锁已被其他 goroutine 占用,则调用的 goroutine 会阻塞,直到锁可用。
  • Unlock() 方法用于释放锁,调用该方法前必须确保当前 goroutine 持有锁。
  • TryLock()方法尝试获取锁,返回是否成功。使用 TryLock 需要谨慎,因为它通常是对互斥锁的误用的迹象。

源码

标志位

mutexLocked:表示锁是否被持有。如果这个标志位被设置,说明锁已经被某个 goroutine 持有。
mutexWoken:表示是否有被唤醒的等待者。如果这个标志位被设置,说明在释放锁的时候有 goroutine 被唤醒。
mutexStarving:表示锁是否处于饥饿模式。在饥饿模式下,锁的所有权会直接从解锁的 goroutine 直接移交给等待队列中的第一个等待者

获取锁

在这里插入图片描述

Lock

func (m *Mutex) Lock() {
	// Fast path: grab unlocked mutex.
	// 快速路径上, 直接拿到锁了, 一般是第一个协程的时候 , 
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
		if race.Enabled {
			race.Acquire(unsafe.Pointer(m))
		}
		return
	}
	// 获取不到, 就去慢路径上了, 自旋等待、正常模式、饥饿模式 操作
	// Slow path (outlined so that the fast path can be inlined)
	m.lockSlow()
}

lockSlow


func (m *Mutex) lockSlow() {
	// 记录 等待开始的事件
	var waitStartTime int64
	// 标记是否是饥饿模式
	starving := false
	// 标志是否 已经唤醒
	awoke := false
	// 自旋迭代次数
	iter := 0
	// 获取当前互斥锁的状态
	old := m.state
	for {
		// Don't spin in starvation mode, ownership is handed off to waiters
		// so we won't be able to acquire the mutex anyway.
		// 检查 当前状态的第0位(mutexLocked 互斥锁)是否为1, 第2位 (mutexStarving 互斥锁饥饿模式) 是否为0, 满足这俩个状态说明当前被锁住,并且不处于饥饿模式
		// 并且 进行函数检查, runtime_canSpin(1、当前goroutine 是可运行状态, 2、当前goroutine不可抢占),来判断是否有资格进行自旋 
		if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
			// 实现互斥锁的自旋等待机制
			// Active spinning makes sense.
			// Try to set mutexWoken flag to inform Unlock
			// to not wake other blocked goroutines.
			// !awoke 表示在此之前没有唤醒过其他等待的 Goroutine
			// old&mutexWoken == 0:表示之前的状态中还没有被唤醒过的标记。
			// old>>mutexWaiterShift != 0:表示有等待的 Goroutine
			// atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) 尝试唤醒, 原子操作, 将mutexWoken位 置为1 
			if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
				atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
				// 唤醒位 置为true, 唤醒了, 当前goroutine 有争夺锁的机会了
			
				awoke = true
			}
			
			
			// 通过自旋等待的方式竞争锁的所有权。
			// 在获取锁失败的情况下, 不至于立即阻塞当前 goroutine,而是通过短暂的自旋等待,期望其他 goroutine 尽快释放锁,以便当前 goroutine 有机会获取到锁
			runtime_doSpin()
			iter++
			old = m.state
			continue
		}
		new := old
		// Don't try to acquire starving mutex, new arriving goroutines must queue.
		//检查当前锁是否处于饥饿模式,如果不是,说明当前 goroutine 是第一个尝试获取锁的,将 mutexLocked 位置为1,表示锁被持有
		if old&mutexStarving == 0 {
			new |= mutexLocked
		}
		// 检查当前锁是否已经被持有或处于饥饿模式。如果是,表示有其他 goroutine 持有锁或已经处于饥饿模式,将 mutexWaiterShift 位加到 new 中,表示有等待者。
		if old&(mutexLocked|mutexStarving) != 0 {
			new += 1 << mutexWaiterShift
		}
		// The current goroutine switches mutex to starvation mode.
		// But if the mutex is currently unlocked, don't do the switch.
		// Unlock expects that starving mutex has waiters, which will not
		// be true in this case.
		// 这段代码用于处理在当前锁状态已经是 starving(饥饿)模式,并且当前锁是被锁住的情况下,将新状态 new 中的 mutexStarving 位设置为1,则将锁切换到饥饿模式
		if starving && old&mutexLocked != 0 {
			new |= mutexStarving
		}
		// 唤醒标志位
		if awoke {
			// The goroutine has been woken from sleep,
			// so we need to reset the flag in either case.
			// 在之前没唤醒过了, 但是始终没拿到锁, 所以后面阶段还是需要被重新唤醒, 这个标志位需要被重置
			if new&mutexWoken == 0 {
				throw("sync: inconsistent mutex state")
			}
			new &^= mutexWoken
		}
		//如果成功表示成功获取了锁。
		if atomic.CompareAndSwapInt32(&m.state, old, new) {
			// old 状态位是0 , 之前没有锁, 说明成功获取了锁, 结束 退出
			if old&(mutexLocked|mutexStarving) == 0 {
				break // locked the mutex with CAS
			}
			// else 如果之前的状态已经锁定了, 后续处理逻辑,主要包括处理饥饿模式、等待队列等情况。
			
			// If we were already waiting before, queue at the front of the queue.
			// 当前goroutine在等待队列中排队, 是否使用LIFO方式排队
			queueLifo := waitStartTime != 0
			// 等待时间为0 , 刚开始排队, 记录下时间
			if waitStartTime == 0 {
				waitStartTime = runtime_nanotime()
			}
			// 等待获取锁, 其中 queueLifo 决定了是否使用 LIFO 排队,最后的参数 1 表示等待一个锁。
			// 1、 当锁处于饥饿模式时,等待锁的 goroutine 将不再与新的 goroutine 竞争锁。而是直接将锁的所有权直接移交给队列中的第一个等待的 goroutine(等待队列的队首) runtime_SemacquireMutex(&m.sema, true, 1) LIFO
			// 2、 正常模式  runtime_SemacquireMutex(&m.sema, false, 1)FIFO
			// 通过runtime 包中, 等待队列
			runtime_SemacquireMutex(&m.sema, queueLifo, 1)
			// 根据等待时间 判断是否处于饥饿模式
			starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
			old = m.state
			//如果等待时间超过了饥饿模式的阈值,且当前锁的状态是饥饿模式,就尝试退出饥饿模式,切换回正常模式。这里需要考虑到可能的竞态条件,因此使用 CAS(Compare-And-Swap)操作
			if old&mutexStarving != 0 {
				// If this goroutine was woken and mutex is in starvation mode,
				// ownership was handed off to us but mutex is in somewhat
				// inconsistent state: mutexLocked is not set and we are still
				// accounted as waiter. Fix that.
				if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
					throw("sync: inconsistent mutex state")
				}
				// 计算状态变化值
				delta := int32(mutexLocked - 1<<mutexWaiterShift)
				// 如果不处于饥饿模式,或者当前 Goroutine 是队列中的第一个等待者,表示需要退出饥饿模式。
				
				if !starving || old>>mutexWaiterShift == 1 {
					// Exit starvation mode.
					// Critical to do it here and consider wait time.
					// Starvation mode is so inefficient, that two goroutines
					// can go lock-step infinitely once they switch mutex
					// to starvation mode.
					delta -= mutexStarving
				}
				// 使用原子操作将互斥锁的状态更新为新值。
				atomic.AddInt32(&m.state, delta)
				break
			}
			awoke = true
			iter = 0
		} else {
		// 获取不到 
			old = m.state
		}
	}

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

Unlock

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)
	}
}

func (m *Mutex) TryLock() bool {
	old := m.state
	if old&(mutexLocked|mutexStarving) != 0 {
		return false
	}

	// There may be a goroutine waiting for the mutex, but we are
	// running now and can try to grab the mutex before that
	// goroutine wakes up.
	if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
		return false
	}

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


func (m *Mutex) unlockSlow(new int32) {
	// 不能解锁 没有锁住的锁
	if (new+mutexLocked)&mutexLocked == 0 {
		fatal("sync: unlock of unlocked mutex")
	}
	
	if new&mutexStarving == 0 {
	// 正常模式
		old := new
		for {
		 	 // 如果没有等待者或者 goroutine 已经被唤醒或者获取了锁,就不需要唤醒任何人。
         
			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, 1)
				return
			}
			old = m.state
		}
	} else {
		// 饥饿模式 , 使用LIFO队列
		// Starving mode: handoff mutex ownership to the next waiter, and yield
		// our time slice so that the next waiter can start to run immediately.
		// Note: mutexLocked is not set, the waiter will set it after wakeup.
		// But mutex is still considered locked if mutexStarving is set,
		// so new coming goroutines won't acquire it.
		runtime_Semrelease(&m.sema, true, 1)
	}
}

怎么 调度 goroutine

对goroutine进行调度是通过runtime保重的调度器来实现的

Mutex 的状态: Mutex 的状态信息存储在 state 字段中,其中高位表示已经锁住的数量,低位表示其他信息(比如等待者的数量等)。

等待者队列: Go 的调度器管理着等待者的队列,当一个 goroutine 尝试获取锁但锁已经被其他 goroutine 占用时,它会被放入调度器的等待队列。

调度器的作用: 调度器会负责管理所有的 goroutine,包括它们的状态、调度和等待队列。当锁被释放时,调度器会决定哪个等待中的 goroutine 会被唤醒,然后有机会获取锁。

自旋和阻塞: 在尝试获取锁时,如果锁已经被其他 goroutine 占用,当前 goroutine 会通过自旋等待(短暂的忙等待)或者阻塞等待(让出 CPU 资源,等待调度器通知)。

总体来说,Mutex 的实现是基于 state 字段和调度器的协同工作。它通过调度器来管理等待者的队列,实现了一种高效的锁竞争和等待机制

runtime 方法

1、 runtime_canSpin(iter int):

作用:该函数用于检查当前 goroutine 是否可以进行自旋等待,以避免阻塞。
用法:在自旋等待的时候调用,避免过多的自旋。

2、runtime_doSpin():

作用:实现自旋等待的具体逻辑,包括执行一定的无用操作,使得当前 goroutine 让出 CPU 时间,增加其他 goroutine 获取锁的机会。
用法:在自旋等待的时候调用。

3、runtime_nanotime():

作用:获取当前时间(纳秒级别)。
用法:在等待过程中记录等待的起始时间,用于判断是否需要切换到饥饿模式。

4、runtime_SemacquireMutex(sema *uint32, lifo bool, skipframes int):

作用:在等待获取锁时使用,该函数封装了对信号量的获取操作,可以阻塞当前 goroutine。
参数:
	sema:信号量指针,用于同步等待。
	lifo:是否使用后进先出(LIFO)方式排队等待。
	skipframes:用于在跟踪时跳过的帧数,以隐藏 runtime_SemacquireMutex 的调用。

5、runtime_Semrelease(sema *uint32, handoff bool, skipframes int):

作用:在释放锁时使用,该函数封装了对信号量的释放操作,用于唤醒等待者。
参数:
	sema:信号量指针,用于同步等待。
	handoff:是否切换到饥饿模式。
	skipframes:用于在跟踪时跳过的帧数,以隐藏 runtime_Semrelease 的调用。

这些 runtime 包中的方法提供了底层的并发控制机制,支持互斥锁的实现。它们用于在不同的情况下实现自旋等待、唤醒等待者以及记录时间等操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1340058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

0.1+0.2≠0.3,揭秘Python自带的Bug

朋友们&#xff0c;问一个简单的问题&#xff1a;0.10.2&#xff1f; 你肯定会说&#xff1a;中国人不骗中国人&#xff0c;0.10.20.3。 但是在Python里&#xff0c;0.10.2≠0.3 &#xff0c;我们今天一起来看看这个&#xff0c;并且看一下解决办法。 离奇的错误 在python里…

解决ELement-UI懒加载三级联动数据不回显(天坑)

最老是遇到这类问题头有点大,最后也是解决了,为铁铁们总结了一下几点 一.查看数据类型是否一致 未选择下 选择下 二.处理数据时使用this.$set方法来动态地设置实例中的属性&#xff0c;以确保其响应式 三.绑定v-if 确保每次重新加载 四.绑定key 五.完整代码

台阶仪在半导体行业中的广泛应用及其重要意义

台阶仪在半导体材料的表征和研究中是一种非常重要的工具。如在半导体材料的制备过程中&#xff0c;一些关键的工艺参数&#xff0c;如温度、压力、气氛等条件的变化&#xff0c;会导致半导体材料的能带结构发生变化&#xff0c;通过使用台阶仪&#xff0c;可以准确测量和分析材…

菜鸟学习vue3笔记-vue hooks初体验

import { ref } from "vue"; export default function () {let a1 ref(1);let a2 ref(5);let c ref(0);function add() {a1.value;a2.value;}return {add,a1,a2,c,}; }<template><div><p>第一个数字{{ a1 }}</p><p>第二个数字{{ a2…

解密Python高级特性:深度探讨装饰器与上下文管理器的魔法

写在开头 在Python编程的奇妙世界里,有两个被誉为编程魔法的特性:装饰器和上下文管理器。它们不仅如同纹章和护盾般赋予代码强大的能力,更是提升代码优雅性和可维护性的法宝。本篇文章将深入研究这两项高级特性,揭示它们的神秘面纱,同时通过丰富的实例展示它们的多样应用…

用C的递归函数求n!-----(C每日一编程)

用递归函数求n&#xff01; 有了上面这个递归公式就能写C代码了。 参考代码&#xff1a; int fac(int n) {if (n 1 || n 0)return 1;else return n * fac(n - 1); } void main() {int n;scanf("%d",&n);int f fac(n);printf("\n%d!%d\n", n, f); …

[Angular] 笔记 11:可观察对象(Observable)

chatgpt: 在 Angular 中&#xff0c;Observables 是用于处理异步数据流的重要工具。它们被广泛用于处理从异步操作中获取的数据&#xff0c;比如通过 HTTP 请求获取数据、定时器、用户输入等。Observables 提供了一种机制来订阅这些数据流&#xff0c;并可以在数据到达时执行相…

python实现一维傅里叶变换——冈萨雷斯数字图像处理

原理 傅立叶变换&#xff0c;表示能将满足一定条件的某个函数表示成三角函数&#xff08;正弦和/或余弦函数&#xff09;或者它们的积分的线性组合。在不同的研究领域&#xff0c;傅立叶变换具有多种不同的变体形式&#xff0c;如连续傅立叶变换和离散傅立叶变换。最初傅立叶分…

Python初学者必须吃透的69个内置函数!

所谓内置函数&#xff0c;就是Python提供的, 可以直接拿来直接用的函数&#xff0c;比如大家熟悉的print&#xff0c;range、input等&#xff0c;也有不是很熟&#xff0c;但是很重要的&#xff0c;如enumerate、zip、join等&#xff0c;Python内置的这些函数非常精巧且强大的&…

docker学习(二十一、network使用示例container、自定义)

文章目录 一、container应用示例1.需要共用同一个端口的服务&#xff0c;不适用container方式2.可用示例3.停掉共享源的容器&#xff0c;其他容器只有本地回环lo地址 总结 二、自定义网络应用示例默认bridge&#xff0c;容器间ip通信默认bridge&#xff0c;容器间服务名不通 自…

FPGA高端项目:SDI 视频+音频编解码,提供工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的 GT 高速接口解决方案我目前已有的SDI编解码方案 3、设计思路和框架设计框图GV8601A均衡EQGTX 时钟要求GTX 调用与控制SMPTE SD/HD/3G-SDISMPTE SD/HD/3G-SDI 接收SMPTE SD/HD/3G-SDI 发送 SDI 视频接收数据处理SDI 音频接收-…

天文观测与计算机技术:共舞在星辰大海

天文观测与计算机技术&#xff1a;共舞在星辰大海 在人类探索宇宙的历程中&#xff0c;天文观测和计算机技术如同一对并肩作战的勇士&#xff0c;共同书写着人类对宇宙的认知。本篇博客将深入探讨这两者如何交织在一起&#xff0c;为人类打开一扇又一扇探索宇宙的窗户。 一、…

羊大师教你如何选择适合孩子的羊奶,孩子成长的关键!

当谈到孩子的健康与成长时&#xff0c;正确的饮食是至关重要的。而在众多的饮品中&#xff0c;羊奶因其独特的营养价值备受家长们的青睐。那么&#xff0c;如何为孩子挑选适合的羊奶成为了一个重要的选择。下面&#xff0c;小编羊大师将为大家介绍如何选择适合孩子的羊奶。 我…

Flask 账号管理列表

Flask 账号管理列表 web/controllers/account/Account.py /index route_account Blueprint( account_page,__name__ )route_account.route( "/index" ) def index():resp_data {}req request.valuespage int( req[p] ) if ( p in req and req[p] ) else 1qu…

【Simulink系列】——用扫地机器人实例快速引入

目录 一、Simulink基本认知 1、模块图建模 2、仿真基本流程 3、调试技巧 二、基于模型的simulink设计实例&#xff08;两轮扫地机器人&#xff09; 1、系统定义与布局 确定建模目的 确定系统组件和接口 确定系统布局 2、建模并验证系统 对组件建模 ①物理组件建模 …

深度学习基础知识神经网络

神经网络 1. 感知机 感知机&#xff08;Perceptron&#xff09;是 Frank Rosenblatt 在1957年提出的概念&#xff0c;其结构与MP模型类似&#xff0c;一般被视为最简单的人工神经网络&#xff0c;也作为二元线性分类器被广泛使用。通常情况下指单层的人工神经网络&#xff0c…

如何使用甘特图进行项目管理?

或许你在工作中或项目启动会议上听说过“甘特图”一词&#xff0c;但对此了解不多。虽然这些图表可能变得相当复杂&#xff0c;但基础知识并不难掌握。通过本文&#xff0c;你将清楚地了解什么是甘特图、何时使用甘特图、创建甘特图的技巧等等。 什么是甘特图&#xff1f; 甘特…

【多线程】常见问题简单总结

目录 1. 竞态条件&#xff08;Race Condition&#xff09; 场景: 解决方法: 2. 死锁&#xff08;Deadlock&#xff09; 场景: 解决方法: 3. 线程饥饿&#xff08;Thread Starvation&#xff09; 场景: 解决方法: 4. 活锁&#xff08;Livelock&#xff09; 场景: 解…

03.MySQL的体系架构

MySQL的体系架构 一、MySQL简介二、MySQL的体系架构三、MySQL的内存结构四、MySQL的文件结构 一、MySQL简介 MySQL是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;由瑞典MySQL AB公司开发&#xff0c;后被Sun公司收购&#xff0c;Sun公司被Oracle…