首页 > 编程语言 >Go 互斥锁 Mutex 源码分析 (一)

Go 互斥锁 Mutex 源码分析 (一)

时间:2024-08-23 15:50:12浏览次数:14  
标签:old sync 互斥 源码 Mutex 自旋 Go new runtime

原创文章,欢迎转载,转载请注明出处,谢谢。


0. 前言

锁作为并发编程中的关键一环,是应该要深入掌握的。

1. 锁

1.1 示例

实现锁很简单,示例如下:

var global int

func main() {
	var mu sync.Mutex
	var wg sync.WaitGroup	

	for i := 0; i < 2; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			mu.Lock()
			global++
			mu.Unlock()
		}(i)
	}

	wg.Wait()
	fmt.Println(global)
}

输出:

2

在 goroutine 中给全局变量 global 加锁,实现并发顺序增加变量。其中,sync.Mutex.Lock() 对变量/临界区加锁,sync.Mutex.Unlock() 对变量/临界区解锁。

1.2 sync.Mutex

我们看 sync.Mutex 互斥锁结构:

type Mutex struct {
	state int32
	sema  uint32
}

其中,state 表示锁的状态,sema 表示信号量。

进入 sync.Mutex.Lock() 查看加锁的方法。

1.2.1 sync.Mutex.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()
}

首先进入 Fast path 逻辑,原子 CAS 操作比较锁状态 m.state 和 0,如果相等则更新当前锁为已加锁状态。这里锁标志位如下:

image

从低(右)到高(左)的三位表示锁状态/唤醒状态/饥饿状态:

const (
	mutexLocked = 1 << iota // mutex is locked
	mutexWoken
	mutexStarving
)

标志位初始值为 0,1 表示状态生效。

前三位之后的位数表示排队等待锁的 goroutine 数目,总共可以允许 1<<(32-3) 个 goroutine 等待锁。

这里假设有两个 goroutine G1 和 G2 抢占锁,其中 G1 通过 Fast path 获取锁,将锁的状态置为 1。这时候 G2 未获得锁,进入 Slow path

func (m *Mutex) lockSlow() {
	var waitStartTime int64
	starving := false
	awoke := false
	iter := 0
	old := m.state
	for {
		// step1: 进入自旋
		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
		}

        ...
    }
}

Slow path 的代码量不大,但涉及状态转换很复杂,不容易看懂。这里拆成每个步骤,根据不同场景分析具体源码。

进入 Mutex.lockSlow(),初始化各个状态位,将当前锁状态赋给变量 old,进入 for 循环,执行第一步自旋逻辑。自旋会独占 CPU,让 CPU 空跑,但是减少了频繁切换 goroutine 带来的内存/时间消耗。如果使用的适当,会节省 CPU 开销,使用的不适当,会造成 CPU 浪费。这里进入自旋是很严苛的,通过三个条件判断能否自旋:

  1. 当前锁是普通模式才能进入自旋。
  2. runtime.sync_runtime_canSpin 需要返回 true:
    • 当前 goroutine 进入自旋的次数小于 4 次;
    • goroutine 运行在多 CPU 的机器上;
    • 当前机器上至少存在一个正在运行的处理器 P 并且处理的运行队列为空;

假设 G2 可以进入自旋,运行 runtime_doSpin()

# src/runtime/lock_futex.go
const active_spin_cnt = 30

# src/runtime/proc.go
//go:linkname sync_runtime_doSpin sync.runtime_doSpin
//go:nosplit
func sync_runtime_doSpin() {
	procyield(active_spin_cnt)
}

# src/runtime/asm_amd64.s
TEXT runtime·procyield(SB),NOSPLIT,$0-0
	MOVL	cycles+0(FP), AX
again:
	PAUSE
	SUBL	$1, AX
	JNZ	again
	RET

自旋实际上是 CPU 执行了 30 次 PAUSE 指令。

自旋是在等待,等待锁释放的过程。假设在自旋期间 G1 已释放锁,更新 m.state 为 0。那么,在 G2 自旋逻辑中 old = m.state 将更新 old 为 0。继续往下看,for 循环中做了什么。

func (m *Mutex) lockSlow() {
	...
	for {
        if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
			...
		}

        // step2: 更新 new,这里 new 为 0
        new := old

		// step2: 继续更新 new
        // -      如果锁为普通锁,更新锁状态为已锁。如果锁为饥饿锁,跳过饥饿锁更新。
        // -      这里更新锁为 1
		if old&mutexStarving == 0 {
			new |= mutexLocked
		}

        // step2:继续更新 new
        // -      如果锁为已锁或饥饿的任何一种,则更新 new 的 goroutine 排队等待位
        // -      这里锁为已释放,new 为 1
		if old&(mutexLocked|mutexStarving) != 0 {
			new += 1 << mutexWaiterShift
		}

        // step2: 继续更新 new
        // -      如果 goroutine 处于饥饿状态,并且当前锁是已锁的,更新 new 为饥饿状态
        // -      这里锁为已释放,new 为 1
        if starving && old&mutexLocked != 0 {
			new |= mutexStarving
		}

        // step2: 继续更新 new
        // -      如果当前 goroutine 是唤醒的,重置唤醒位为 0
        // -      goroutine 不是唤醒的,new 为 1
        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
		}

        // step3: CAS 比较 m.state 和 old,如果一致则更新 m.state 到 new
        // -      这里 m.state = 0,old = 0,new = 1
        // -      更新 m.state 为 new,当前 goroutine 获得锁
        if atomic.CompareAndSwapInt32(&m.state, old, new) {
            // 如果更新锁之前的状态不是饥饿或已锁,表示当前 goroutine 已获得锁,跳出循环。
			if old&(mutexLocked|mutexStarving) == 0 {
				break // locked the mutex with CAS
			}
            ...
        }
    }
}

这里将自旋后的逻辑简化为两步,更新锁的期望状态 new 和通过原子 CAS 操作更新锁。这里的场景不难,我们可以简化上述流程为如下示意图:

image

2. 小结

本文介绍了 Go 互斥锁的基本结构,并且给出一个抢占互斥锁的基本场景,通过场景从源码角度分析互斥锁。


标签:old,sync,互斥,源码,Mutex,自旋,Go,new,runtime
From: https://www.cnblogs.com/xingzheanan/p/18376083

相关文章

  • Go实现一个五子棋功能
    参考https://juejin.cn/post/6847902215575699464packagemainimport( "fmt" "math/rand" "strconv" "strings" "time")typehanduintconst( NilHandhand=iota//空白 BlackHand//黑手 Wh......
  • 智能电子名片小程序源码系统 人人可创建属于自己的电子名片 带完整的搭建教程
    系统概述在当今数字化时代,名片已经不再局限于传统的纸质形式。智能电子名片小程序源码系统的出现,为人们提供了一种更加便捷、高效、个性化的名片管理方式。本文将深入介绍该源码系统的系统概述和特色功能,帮助读者更好地了解这一创新工具。代码示例系统特色功能一览名片设......
  • 本地生活同城便民信息小程序源码系统 带完整的安装代码包以及搭建部署教程
    系统概述本地生活同城便民信息小程序源码系统是一款专为本地生活服务打造的综合性平台。它通过整合各类本地商家和服务资源,为用户提供便捷、高效的生活服务信息查询和交易渠道。该系统采用先进的技术架构,具备高度的稳定性和扩展性,能够适应不断变化的市场需求。同时,它还注重用......
  • [每周一更]-(第111期):从零开始:如何在 CentOS 上源码编译安装 PHP 7.4
    文章目录系统信息:0、安装版本:1、下载/解压2、安装依赖3、配置autoconf4、配置参数5、编译和安装6、验证安装的插件6.1、配置php.ini6.2、配置opcache7、错误7.1Failedtoconnectto2a03:2880:f10e:83:face:b00c:0:25de:Networkisunreachable7.1.1禁用yum使用I......
  • 基于Java+Vue的采购管理系统:提高决策效率(项目源码)
       前言:采购管理系统是一个综合性的管理平台,旨在提高采购过程的效率、透明度,并优化供应商管理。以下是对各个模块的详细解释:一、供应商准入供应商注册:供应商通过在线平台进行注册,填写基本信息和资质文件。资质审核:系统对供应商提交的资质文件进行自动或人工审核,确保供......
  • JSP基于SSM框架的高校网络教学平台0qyf5(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表系统功能:教师,学生,课程资料,资料分类,作业信息,作业提交,作业批改,课程信息,选课信息,课程签到技术要求:开发语言:JSP前端使用:HTML5,CSS,JSP动态网页技术后端......
  • JSP基于SSM框架的服装租赁系统606b8程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表系统功能:用户,采购员,清洁员,服装类型,服装信息,服装租赁,采购入库,服装报废,服装归还,清洁分配,清洁服装,通知公告技术要求:开发语言:JSP前端使用:HTML5,CSS,JS......
  • JSP基于SSM的校园音乐平台vnzr8(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表系统功能:用户,校园歌手,明星歌手,歌曲类别,校园歌曲,歌曲mv,歌手歌曲开题报告内容一、课题背景与意义随着互联网技术的快速发展,网络音乐平台已成为人们日常生......
  • JSP基于ssm的校园社团管理系统7wp38程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表系统功能:用户,医生,医院类型,社区医院,接种疫苗,疫苗类型,预约信息开题报告内容一、项目背景与意义随着高等教育的快速发展,校园社团作为学生第二课堂的重要组......
  • 【Python爬虫实战】天气数据爬取+数据可视化(完整代码)_爬取天气预报数据并做可视化分析
    一、选题的背景随着人们对天气的关注逐渐增加,天气预报数据的获取与可视化成为了当今的热门话题,天气预报我们每天都会关注,天气情况会影响到我们日常的增减衣物、出行安排等。每天的气温、相对湿度、降水量以及风向风速是关注的焦点。通过Python网络爬虫爬取天气预报让我们快......