go并发与锁之sync.Mutex入门

news2025/5/29 11:48:22

sync.Mutex

原理:一个共享的变量,哪个线程握到了,哪个线程可以执行代码

功能:一个性能不错的悲观锁,使用方式和Java的ReentrantLock很像,就是手动Lock,手动UnLock。

使用例子:

var mu sync.Mutex // 管理协程用的,主要是让协程同意结束后再运行调用协程
var cnt int
func add() {cnt++}
var wg sync.WaitGroup
func main() {
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			defer mu.Unlock()
			mu.Lock()
			add()
		}()
	}
	wg.Wait()
	fmt.Print(cnt)
}
实现原理:

直接看源码好了,

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()是一个逻辑很多的方法,具体逻辑是:

  1. 自旋尝试:
    • 若当前是正常模式且锁持有时间较短,当前goroutine会自旋(循环检查锁状态),尝试避免立即阻塞。
    • 自旋条件:多核CPU、当前未处于饥饿模式、等待队列为空或自旋次数未超过阈值。
  2. 更新等待计数:
    • 通过原子操作增加state中的等待goroutine计数(高30位)。
  3. 进入阻塞或饥饿模式:
    • 正常模式:若自旋失败,将当前goroutine加入信号量等待队列(sema),并调用runtime_SemacquireMutex阻塞。
    • 饥饿模式:若当前goroutine等待时间超过阈值(1ms),触发饥饿模式。此时新来的goroutine直接进入队列尾部,不再自旋。
func (m *Mutex) lockSlow() {
    // 初始化变量操作,省略...
	for {
        // 这部分处理自旋尝试获取锁的逻辑
		if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
			 // 省略...
			runtime_doSpin()
			continue
		}
		new := old
        // 如果不是饥饿模式,尝试获取锁(new |= mutexLocked)
		if old&mutexStarving == 0 {
			new |= mutexLocked
		}
        // 如果锁已被占用或处于饥饿模式,增加等待者计数(new += 1 << mutexWaiterShift)
		if old&(mutexLocked|mutexStarving) != 0 {
			new += 1 << mutexWaiterShift
		}
		// 如果当前 goroutine 处于饥饿状态且锁被占用,切换到饥饿模式(new |= mutexStarving)
		if starving && old&mutexLocked != 0 {
			new |= mutexStarving
		}
        // 如果当前 goroutine 是被唤醒的:确保 mutexWoken 标志已设置(否则抛出异常);清除 mutexWoken 标志(new &^= mutexWoken)
		if awoke {
			if new&mutexWoken == 0 {
				throw("sync: inconsistent mutex state")
			}
			new &^= mutexWoken
		}
        // 尝试用 CAS 更新锁状态
		if atomic.CompareAndSwapInt32(&m.state, old, new) {
			if old&(mutexLocked|mutexStarving) == 0 {
				break 
			}
            // 决定排队位置:如果是第一次等待(waitStartTime == 0),记录开始等待时间;否则使用 LIFO 顺序(queueLifo = true)
			queueLifo := waitStartTime != 0
			if waitStartTime == 0 {
				waitStartTime = runtime_nanotime()
			}
            // runtime_SemacquireMutex 将 goroutine 放入等待队列并阻塞
			runtime_SemacquireMutex(&m.sema, queueLifo, 2)
            // 被唤醒后:检查是否等待超时(超过 1ms),更新饥饿状态;重新读取锁状态
			starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
			old = m.state
            // 如果是饥饿模式:
			if old&mutexStarving != 0 {
                // 检查状态是否一致
				if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
					throw("sync: inconsistent mutex state")
				}
                // 计算状态增量: 设置 mutexLocked;减少等待者计数;如果不再饥饿或只有一个等待者,退出饥饿模式
				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
		}
	}
}

在这里插入图片描述
Goroutine A 获取锁(Lock()快速路径成功)。
Goroutine B 尝试获取锁,进入慢速路径:
自旋数次后失败,增加等待计数,进入队列阻塞。
Goroutine A 释放锁(Unlock()):
唤醒Goroutine B,新来的Goroutine C可与B竞争锁。

在这里插入图片描述
Goroutine B 等待超过1ms,触发饥饿模式。
Goroutine C 新到达,直接进入队列尾部,不自旋。
Goroutine A 释放锁:
直接将锁交给队列头部的Goroutine B。
Goroutine B 释放锁后,若队列中无等待者,退出饥饿模式。

作者:ShanekAI
链接:https://juejin.cn/post/7488246529430487077
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

整体逻辑大致来说就是:Go 的 sync.Mutex 在竞争不激烈时,会采用短暂的 自旋锁 机制。自旋锁允许 Goroutine 在一小段时间内忙等待,而不是立即进入阻塞状态。这种策略避免了频繁的上下文切换开销。如果激烈的话,就进入饥饿模式,更改了逻辑,在饥饿模式里,停止自旋,直接将当前协程加入等待队列。当前线程执行完毕了,如果是饥饿模式,会把队列里第一个拿出来唤醒。

名词解释:

自旋:就是忙等,就是最简单的例子:

for state == 1{} // 不停地遍历,就好像在不停地自我旋转一样;直到state被其他线程修改了,才停止

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

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

相关文章

[Java恶补day8] 3. 无重复字符的最长子串

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”&#xff0c;所以其长度为 3。 示例 2: 输入: s “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “…

LabVIEW教学用开发平台

一、培训目标 基础编程&#xff1a;掌握 LabVIEW 数据类型、程序结构、子 VI 设计与调试技巧。 硬件通信&#xff1a;精通 RS-232/485、TCP/IP、Modbus、PLC 等工业通信协议及实现。 高级设计模式&#xff1a;熟练运用状态机、生产者 - 消费者模式构建复杂测控系统。 项目实…

Package Size Comparison – 6 Leads

Package Size Comparison 6 LeadsTSOP SOT SM SMT SOT23 SC-74 SC-59 SC-88 SOT363 US6 UMT6 SC-70 SOT563 ES EMT SC-75-6

python打卡day38

Dataset和DataLoader 知识点回顾&#xff1a; Dataset类的__getitem__和__len__方法&#xff08;本质是python的特殊方法&#xff09;Dataloader类minist手写数据集的了解 作业&#xff1a;了解下cifar数据集&#xff0c;尝试获取其中一张图片 在遇到大规模数据集时&#xff0c…

vLLM 核心技术 PagedAttention 原理详解

本文是 vLLM 系列文章的第二篇&#xff0c;介绍 vLLM 核心技术 PagedAttention 的设计理念与实现机制。 vLLM PagedAttention 论文精读视频可以在这里观看&#xff1a;https://www.bilibili.com/video/BV1GWjjzfE1b 往期文章&#xff1a; vLLM 快速部署指南 1 引言&#xf…

《软件工程》第 2 章 -UML 与 RUP 统一过程

在软件工程领域&#xff0c;UML&#xff08;统一建模语言&#xff09;与 RUP&#xff08;统一过程&#xff09;是进行面向对象软件开发的重要工具和方法。接下来&#xff0c;我们将深入探讨第 2 章的内容&#xff0c;通过案例和代码&#xff0c;帮助大家理解和掌握相关知识。 …

(转)Docker与K8S的区别

1 定义角度 Docker是一种开放源码的应用容器引擎&#xff0c;允许开发人员将其应用和依赖包打包成可移植的容器/镜像中&#xff1b;然后&#xff0c;发布到任何流行的 Linux 或 Windows 机器上&#xff0c;也能实现虚拟化。该容器完全使用沙箱机制&#xff0c;彼此之间没有任何…

商用密码 vs 普通密码:安全加密的核心区别

商用密码 vs 普通密码&#xff1a;安全加密的核心区别 一. 引言&#xff1a;密码的世界二. 什么是普通密码&#xff1f;三. 什么是商用密码&#xff1f;四. 普通密码 vs 商用密码&#xff1a;核心区别五. 选择合适的密码方案六. 结语 前言 肝文不易&#xff0c;点个免费的赞和…

MYSQL中的分库分表及产生的分布式问题

分库分表是分布式数据库架构中常用的优化手段&#xff0c;用于解决单库单表数据量过大、性能瓶颈等问题。其核心思想是将数据分散到多个数据库&#xff08;分库&#xff09;或多个表&#xff08;分表&#xff09;中&#xff0c;以提升系统的吞吐量、查询性能和可扩展性。 一&am…

投影机三色光源和单色光源实拍对比:一场视觉体验的终极较量

一、光源技术&#xff1a;从 “单色模拟” 到 “三色原生” 的进化 &#xff08;一&#xff09;单色光源&#xff1a;白光的 “色彩魔术” 单色光源投影机采用单一白光作为基础光源&#xff0c;通过LCD上出现色彩呈现颜色。这种技术路线的优势在于成本可控&#xff0c;早期被广…

电子电气架构 --- 下一代汽车电子电气架构中的连接性

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…

手写Tomcat(一)

一、Tomcat简介 Tomcat 服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP 程序的首选。 1.1 Tomcat基本架构 Servlet接口文件中定义的方法有以下…

【机器学习基础】机器学习入门核心算法:支持向量机(SVM)

机器学习入门核心算法&#xff1a;支持向量机&#xff08;SVM&#xff09; 一、算法逻辑1.1 基本概念1.2 核心思想线性可分情况 二、算法原理与数学推导2.1 原始优化问题2.2 拉格朗日对偶2.3 对偶问题2.4 核函数技巧2.5 软间隔与松弛变量 三、模型评估3.1 评估指标3.2 交叉验证…

定时清理流媒体服务器录像自动化bash脚本

定时清理流媒体服务器保存录像文件夹 首先创建一个文件,解除读写权限 touch rm_videos.sh chmod 777 rm_videos.sh将内容复制进去&#xff0c;将对应文件夹等需要修改的内容&#xff0c;根据自己的实际需求进行修改 #!/bin/bash# 设置目标目录&#xff08;修改为你的实际路…

Logi鼠标切换桌面失效

Mac上习惯了滑屏切换桌面&#xff0c;所以Logi鼠标也定制了切换桌面的动作&#xff0c;有一天发现这个动作失效了&#xff0c;且只有切换桌面的动作失效。 发现Logi Options出现了这个提示&#xff0c;如图所示&#xff08;具体原因未知&#xff0c;已配置不自动更新版本&…

2025年全国青少年信息素养大赛复赛C++集训(15):因子问题(题目及解析)

2025年全国青少年信息素养大赛复赛C集训&#xff08;15&#xff09;&#xff1a;因子问题&#xff08;题目及解析&#xff09; 题目描述 任给两个正整数N、M&#xff0c;求一个最小的正整数a&#xff0c;使得a和(M-a)都是N的因子。 时间限制&#xff1a;10000 内存限制&…

Vue3进阶教程:1.初次了解vue

1.初次了解vue vue文件目录和各个文件在这里不做介绍 此课程对针对有点vue基础的同学&#xff0c;或者看过我上部分vue的教程 与之前我的Vue教程不同的是&#xff0c;写法和内容有区别 真正的了解Vue3 1.创建vue组件 1.npm create vuelatest 2.取名 3.TS要选上 4.其他先不选 5…

WordPress免费网站模板下载

大背景图免费wordpress建站模板 这个wordpress模板设计以简约和专业为主题&#xff0c;旨在为用户提供清晰、直观的浏览体验。以下是对其风格、布局和设计理念的详细介绍&#xff1a; 风格 简约现代&#xff1a;整体设计采用简约风格&#xff0c;使用了大量的白色和灰色调&am…

【深度学习新浪潮】以图搜地点是如何实现的?(含大模型方案)

1. 以图搜地点的实现方式有哪些? 扫描手机照片中的截图并识别出位置信息,主要有以下几种实现方式: 通过照片元数据获取: 原理:现代智能手机拍摄的照片通常会包含Exif(Exchangeable Image File)元数据。Exif中除了有像素信息之外,还包含了光圈、快门、白平衡、ISO、焦距…

element的el-table翻页选中功能

el-table翻页选中功能 row-key"enterpriseWorkerId" selection-change"handleSelectionChange"<el-table-column type"selection" :reserve-selection"true" width"55"></el-table-column>stuMultipleList: []…