当前位置: 首页 > news >正文

go中mutex的sema信号量是什么?

先看下go的sync.mutex是什么

type Mutex struct {state int32sema  uint32
}

这里面有个sema,这个就是信号量。

什么是信号量?

什么是信号量?_kina100的博客-CSDN博客

其实通俗的来说,信号量就是信号灯,但是他不是个灯,他是一个变量,这个变量通过值来承担信号功能,比如值为0的时候,我们称之为假,值为1的时候我们称之为真,这就是信号量。

sema在mutex里面是怎么实现的?

在go的sync.mutex里面 sema表面上来看就是个uint32,但是实际上他底层是一个semaRoot结构体:

type semaRoot struct {lock  mutextreap *sudog        // root of balanced tree of unique waiters.nwait atomic.Uint32 // Number of waiters. Read w/o the lock.
}

semaRoot里面有三个成员,分别是:

1,lock,这是一个mutex类型,要注意的是,这不是sync包里面的mutex,这是runtime2包里面的mutex,关于runtime包里的mutex其实用的地方也非常多,可以看看这个go中runtime包里面的mutex是什么?runtime.mutex解析_kina100的博客-CSDN博客

 这个lock锁主要的作用是保护semaRoot结构体的访问,防止多个goroutine竞争访问semaroot的时候出现并发问题。

2, treap实际上是一个平衡树(balanced tree)的root节点,他的主要作用其实就是存等待这个锁的goroutine,当一个g进来请求锁的时候,如果锁没有得到,就开始进入等待,他会被包装成一个sudog然后进入到treap里面,启动休眠,当上一个拿到锁的g释放锁后,就会从treap里取出一个sudog唤醒获得锁

3,nwait记录下现在等待该锁的g的数量,原则上来说和treap的数量是一致的

如何加锁?

先看一个重要的方法,如何控制sema信号量

// 获取信号量
func cansemacquire(addr *uint32) bool {for {// 这里说明一下,sema如果大于0,说明资源充足不需要竞争,如果sema等于0,代表资源紧张,需要互斥竞争同一资源了,协程若没竞争到资源就进入等待状态了// 这里的sema的数值是在启用的时候就已经初始化设定的// 也就说,如果我们不设定sema数值,而他的初始值就是0,那么sema锁在这一步永远都是renturn false的,反过来说,他已经退化成一个只有treap的队列,这个很重要,因为在go的底层,很多地方都这么用,他不用sema锁,而是用了semaroot结构体当做一个存储g的队列,比如sync.mutex就是这么用的v := atomic.Load(addr)if v == 0 {// 拿不到,你回去等着吧-->包装成sudog,进入treap进行等候return false}// 交换数值,-1,返回trueif atomic.Cas(addr, v, v-1) {return true}}
}

再看加锁的方法:

func semacquire1(addr *uint32, lifo bool, profile semaProfileFlags, skipframes int, reason waitReason) {// 获取当前操作该锁的goroutinegp := getg()// gp.m.curg  就是指向当前线程(M)上正在执行的 goroutine 的指针。// 判断获取的g是否是g所属m当前运行的g,防止你正在操作锁的时候,m已经切换到下一个g了if gp != gp.m.curg {throw("semacquire not on the G stack")}// 尝试获取sema信号,获取成功就返回,意思是拿到锁了if cansemacquire(addr) {return}// 没拿到锁// 初始化创建一个sudog对象s := acquireSudog()// 获取全局的sematable的根节点,这个地方有点难以理解,go的整个全局最大能同时存在251个semaroot,实际上在快速处理的情况下,go很难同时把251个都塞满,极端情况下塞满的话,就得考虑分布式拆分服务了,单个服务已经庞大到需要同时存在251把锁,这服务的复杂度难以想象root := semtable.rootFor(addr)t0 := int64(0)s.releasetime = 0s.acquiretime = 0s.ticket = 0// 这个是阻塞分析用的,一般来说不用管,除非你搞底层研究,阻塞分析需要记录时间,这里的逻辑都是处理时间的if profile&semaBlockProfile != 0 && blockprofilerate > 0 {t0 = cputicks()s.releasetime = -1}if profile&semaMutexProfile != 0 && mutexprofilerate > 0 {if t0 == 0 {t0 = cputicks()}s.acquiretime = t0}// 循环处理for {// 按一定的规则判断锁的顺序,如果不按这个顺序,直接判定为死锁,一般不用管,必须开启GOEXPERIMENT=staticlockranking才有这玩意,这玩意默认关闭lockWithRank(&root.lock, lockRankRoot)// 等待锁的g的计数器+1root.nwait.Add(1)// 再次尝试获取锁。成功就退出循环,没啥好说的if cansemacquire(addr) {root.nwait.Add(-1)unlock(&root.lock)break}// 再次尝试也没拿到锁,进入treap的那个队列root.queue(addr, s, lifo)// 执行gopark,这方法非常重要,但是不需要关注,gopark是go语言底层的一个方法,他的作用是让goroutine挂起等待,换个说法就是休眠,等待被唤醒。它广泛存在于go底层中,但是因为是底层,所以一般来说和应用开发员关系不大,只需要知道他的作用是让g休眠就行goparkunlock(&root.lock, reason, traceEvGoBlockSync, 4+skipframes)// 从阻塞中被唤醒了,开始获取锁,没有获取成功,继续for循环if s.ticket != 0 || cansemacquire(addr) {break}}// 依然是阻塞分析不用管if s.releasetime > 0 {blockevent(s.releasetime-t0, 3+skipframes)}// 释放sudog,已经拿到锁就释放了releaseSudog(s)
}
如何解锁?
func semrelease1(addr *uint32, handoff bool, skipframes int) {// 通过addr在全局的sematable的里找对应的semaroot,和加锁那边一样root := semtable.rootFor(addr)// 给sema信号+1,意思是释放锁atomic.Xadd(addr, 1)// 查是否有等待的 Goroutine,即等待在锁上的 Goroutine 数量。如果没有等待的 Goroutine,则返回,不需要唤醒其他 Goroutine。if root.nwait.Load() == 0 {return}// 对semaroot里面的lock进行操作上锁,防止冲突lockWithRank(&root.lock, lockRankRoot)// 再次检查是否有等待的gif root.nwait.Load() == 0 {//如果没有等待的g//解锁semaroot的lockunlock(&root.lock)return}// 如果有等待的g// 从等待队列里取出一个等待的sudog,让他开始他的逻辑s, t0 := root.dequeue(addr)if s != nil {// 如果treap里面不为空,取出sudog成功,就把等待数量-1root.nwait.Add(-1)}//解锁semaroot的lockunlock(&root.lock)if s != nil {// 检测用的,不用管acquiretime := s.acquiretimeif acquiretime != 0 {mutexevent(t0-acquiretime, 3+skipframes)}if s.ticket != 0 {throw("corrupted semaphore ticket")}if handoff && cansemacquire(addr) {s.ticket = 1}readyWithTime(s, 5+skipframes)// 当g的m不持有其他锁的时候才允许调度if s.ticket == 1 && getg().m.locks == 0 {// 行协程的切换操作,将当前 Goroutine 切换出执行,并且将等待的 Goroutine 放入当前的 P 的本地运行队列,以便被尽快执行。// 这里会优先分配给本地队列,在饥饿状态下,切换非常的直接,会直接让切换的g使用当前g没有用完的时间片goyield()}}
}

相关文章:

go中mutex的sema信号量是什么?

先看下go的sync.mutex是什么 type Mutex struct {state int32sema uint32 } 这里面有个sema,这个就是信号量。 什么是信号量? 什么是信号量?_kina100的博客-CSDN博客 其实通俗的来说,信号量就是信号灯,但是他不是…...

LeetCode笔记:Weekly Contest 360

LeetCode笔记:Weekly Contest 360 0. 吐槽1. 题目一 1. 解题思路2. 代码实现 2. 题目二 1. 解题思路2. 代码实现 3. 题目三 1. 解题思路2. 代码实现 4. 题目四 1. 解题思路2. 代码实现 比赛链接:https://leetcode.com/contest/weekly-contest-360/ 0.…...

【树DP】2021ICPC南京 H

Problem - H - Codeforces 题意&#xff1a; 思路&#xff1a; 这题应该算是铜牌题 铜牌题 简单算法 基础思维 简单复盘一下思路 首先&#xff0c;我们发现有个很特殊的条件&#xff1a; ti < 3 然后看一下样例&#xff1a; 注意到&#xff0c;对于一个结点 u &#…...

Leedcode19. 删除链表的倒数第 N 个结点

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例 3&#xff1a; 输入&#xff1…...

Mysql-索引查询相关

一、单表查询 1.1 二级索引为null 不论是普通的二级索引&#xff0c;还是唯一二级索引&#xff0c;它们的索引列对包含 NULL 值的数量并不限制&#xff0c;所以我们采用key IS NULL 这种形式的搜索条件最多只能使用 ref 的访问方法&#xff0c;而不是 const 的访问方法 1.2 c…...

C++ Pimpl

Pimpl(Pointer to implementation&#xff0c;指向实现的指针) 是一种减少代码依赖和编译时间的C编程技巧&#xff0c;其基本思想是将一个外部可见类(visible class)的实现细节&#xff08;一般是所有私有的非虚成员&#xff09;放在一个单独的实现类(implementation class)中&…...

rust学习-类型转换

基本类型转换 // 不显示类型转换产生的溢出警告。 #![allow(overflowing_literals)]fn main() {let decimal 65.4321_f32;// 错误&#xff01;不提供隐式转换// let integer: u8 decimal;// 可以显式转换let integer decimal as u8;let character integer as char;println…...

算法通过村第四关-栈青铜笔记|手写栈操作

文章目录 前言1. 栈的基础概要1.1 栈的特征1.2 栈的操作1.3 Java中的栈 2. 栈的实现&#xff08;手写栈&#xff09;2.1 基于数组实现2.2 基于链表实现2.3 基于LinkedList实现 总结 前言 提示&#xff1a;我自己一个人的感觉很好 我并不想要拥有你 除非你比我的独处更加宜人 --…...

Python计算加速利器

迷途小书童的 Note 读完需要 6分钟 速读仅需 2 分钟 1 简介 Python 是一门应用非常广泛的高级语言&#xff0c;但是&#xff0c;长久以来&#xff0c;Python的运行速度一直被人诟病&#xff0c;相比 c/c、java、c#、javascript 等一众高级编程语言&#xff0c;完全没有优势。 那…...

PyTorch 深度学习实践 第10讲刘二大人

总结&#xff1a; 1.输入通道个数 等于 卷积核通道个数 2.卷积核个数 等于 输出通道个数 1.单通道卷积 以单通道卷积为例&#xff0c;输入为&#xff08;1,5,5&#xff09;&#xff0c;分别表示1个通道&#xff0c;宽为5&#xff0c;高为5。假设卷积核大小为3x3&#xff0c…...

Linux特殊指令

目录 1.dd命令 2.mkfs格式化 3.df命令 4.mount实现硬盘的挂载 5.unshare 1.dd命令 dd命令可以用来读取转换并输出数据。 示例一&#xff1a; if表示infile&#xff0c;of表示outfile。这里的/dev/zero是一个特殊文件&#xff0c;会不断产生空白数据。 bs表示复制一块的大…...

MPI之主从模式的一般编程示例

比如&#xff0c;我们可以选举0号进程为master进程&#xff0c;其余进程为slaver进程 #include "mpi.h" #include <unistd.h> #include <iostream>int main(int argc, char *argv[]) {int err MPI_Init(&argc,&argv);int rank,size;MPI_Comm_r…...

基于野狗算法优化的BP神经网络(预测应用) - 附代码

基于野狗算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于野狗算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.野狗优化BP神经网络2.1 BP神经网络参数设置2.2 野狗算法应用 4.测试结果&#xff1a;5.Matlab代码 摘要…...

C语言面向对象的编程思想

面向对象编程 面向对象编程Object-Oriented Programming&#xff0c;OOP&#xff09; 作为一种新方法&#xff0c;其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征&#xff0…...

MPI之非阻塞通信中通信完成检测接口简介

在之前的文章中&#xff0c;简单的写了一个非阻塞的通信代码介绍最最基本的使用&#xff1a; int main(int argc, char *argv[]) {int err MPI_Init(&argc,&argv);int rank,size;MPI_Comm_rank(MPI_COMM_WORLD,&rank);MPI_Comm_size(MPI_COMM_WORLD, &size);…...

Excel:如何实现分组内的升序和降序?

一、POWER 1、构建辅助列D列&#xff0c;在D2单元格输入公式&#xff1a; -POWER(10,COUNTA($A$2:A2)3)C2 2、选中B1:D10&#xff0c;注意不能宣导A列的合并单元格&#xff0c;进行以下操作&#xff1a; 3、删除辅助列即可 二、COUNTA 第一步&#xff0c;D2建立辅助列&#xf…...

深度学习论文: Segment Any Anomaly without Training via Hybrid Prompt Regularization

深度学习论文: Segment Any Anomaly without Training via Hybrid Prompt Regularization Segment Any Anomaly without Training via Hybrid Prompt Regularization PDF: https://arxiv.org/pdf/2305.10724.pdf PyTorch代码: https://github.com/shanglianlm0525/CvPytorch Py…...

【算法训练-字符串】一 最长无重复子串

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是最长无重复子串或最长无重复子数组&#xff0c;这类题目出现频率还是很高的。 最长无重复子串【MID】 先来看字符串数据结构的题目 题干 解题思…...

【数据结构】手撕顺序表

一&#xff0c;概念及结构 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储&#xff1b; 在数组上完成数据的增删查改。 1&#xff0c; 静态顺序表&#xff1a;使用定长数组存储元素。 2.&#xff0c;动态顺序表&#xff1…...

景联文科技数据标注:人体关键点标注用途及各点的位置定义

人体关键点标注是一种计算机视觉任务&#xff0c;指通过人工的方式&#xff0c;在指定位置标注上关键点&#xff0c;例如人脸特征点、人体骨骼连接点等&#xff0c;常用来训练面部识别模型以及统计模型。这些关键点可以表示图像的各个方面&#xff0c;例如角、边或特定特征。在…...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

C语言中提供的第三方库之哈希表实现

一. 简介 前面一篇文章简单学习了C语言中第三方库&#xff08;uthash库&#xff09;提供对哈希表的操作&#xff0c;文章如下&#xff1a; C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...

OD 算法题 B卷【正整数到Excel编号之间的转换】

文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的&#xff1a;a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor

1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...

Linux基础开发工具——vim工具

文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...