什么是锁,为什么使用锁
用俗语来说,锁意味着一种保护,对资源的一种保护,在程序员眼中,这个资源可以是一个变量,一个代码片段,一条记录,一张数据库表等等。

就跟小孩需要保护一样,不保护的话小孩会收到伤害,同样的使用锁的原因是资源不保护的话,可能会受到污染,在并发情况下,多个人对同一资源进行操作,有可能导致资源不符合预期的修改。

常见的锁的种类
锁的种类细分的话,非常多,主要原因是从不同角度看,对锁的定义不一样,我这里总结了一下,画一个思维脑图,大家了解一下。

我个人认为锁都可以归为一下四大类,其它的叫法不同只是因为其实现方式或者应用场景而得名,但本质上上还是下面的这四大类中一种。

其它各种类的锁总结如下,这些锁只是为了高性能,为了各种应用场景在代码实现上做了很多工作,因此而得名,关于他们的资料很多

更多锁的详细解释参考我github的名词描述,这里不在赘述,地址如下:

https://github.com/sunpengwei1992/java_common/tree/master/src/lock
Go中的锁使用和实现分析
Go的代码库中为开发人员提供了一下两种锁:

互斥锁 sync.Mutex
读写锁 sync.RWMutex
第一个互斥锁指的是在Go编程中,同一资源的锁定对各个协程是相互排斥的,当其中一个协程获取到该锁时,其它协程只能等待,直到这个获取锁的协程释放锁之后,其它的协程才能获取。

第二个读写锁依赖于互斥锁的实现,这个指的是当多个协程对某一个资源都是只读操作,那么多个协程可以获取该资源的读锁,并且互相不影响,但当有协程要修改该资源时就必须获取写锁,如果获取写锁时,已经有其它协程获取了读写或者写锁,那么此次获取失败,也就是说读写互斥,读读共享,写写互斥。

Go中关于锁的接口定义如下:,该接口的实现就是上面的两个锁种类,篇幅有限,这篇文章主要是分析一下互斥锁的使用和实现,因为RWMutex也是基于Mutex的,大家可以参考文章自行学习一下。

type Locker interface {Lock()Unlock()
}
type Mutex struct {state int32 //初始值默认为0sema  uint32 //初始值默认为0
}

Mutex使用也非常的简单,,声明一个Mutex变量就可以直接调用Lock和Unlock方法了,如下代码实例,但使用的过程中有一些注意点,如下:

同一个协程不能连续多次调用Lock,否则发生死锁
锁资源时尽量缩小资源的范围,以免引起其它协程超长时间等待
mutex传递给外部的时候需要传指针,不然就是实例的拷贝,会引起锁失败
善用defer确保在函数内释放了锁
使用-race在运行时检测数据竞争问题,go test -race …,go build -race …
善用静态工具检查锁的使用问题
使用go-deadlock检测死锁,和指定锁超时的等待问题(自己百度工具用法)
能用channel的场景别使用成了lock

var lock sync.Mutexfunc MutexStudy(){//获取锁lock.Lock()//业务逻辑操作time.Sleep(1 * time.Second)//释放锁defer lock.Unlock()
}

我们了解了Mutext的使用和注意事项,那么具体原理是怎么实现的呢?运用到了那些技术,下面一起分析一下Mutex的实现原理。

Mutex实现中有两种模式,1:正常模式,2:饥饿模式,前者指的是当一个协程获取到锁时,后面的协程会排队(FIFO),释放锁时会唤醒最早排队的协程,这个协程会和正在CPU上运行的协程竞争锁,但是大概率会失败,为什么呢?因为你是刚被唤醒的,还没有获得CPU的使用权,而CPU正在执行的协程肯定比你有优势,如果这个被唤醒的协程竞争失败,并且超过了1ms,那么就会退回到后者(饥饿模式),这种模式下,该协程在下次获取锁时直接得到,不存在竞争关系,本质是为了防止协程等待锁的时间太长。

两种模式都了解了,我们再来分析一下几个核心常量,代码如下:

const (mutexLocked = 1 << iota //1, 0001 最后一位表示当前锁的状态,0未锁,1已锁 mutexWoken //2, 0010,倒数第二位表示当前锁是否会被唤醒,0唤醒,1未唤醒mutexStarving //4, 0100 倒数第三位表示当前对象是否为饥饿模式,0正常,1饥饿mutexWaiterShift = iota //3 从倒数第四位往前的bit表示排队的gorouting数量starvationThresholdNs = 1e6 // 饥饿的阈值:1ms
)

//Mutex中的变量,这里主要是将常量映射到state上面
state //0代表未获取到锁,1代表得到锁,2-2^31表示gorouting排队的数量的
sema //非负数的信号量,阻塞协程的依据
这几个变量你要是都弄白了,那么代码看起来就相对好理解一些了,整个Lock的源码较长,我将注释写入代码中,方便大家理解,整个锁的过程其实分为三部分,建议大家参考源码和我的注释一块学习。

直接获取锁,返回
自旋和唤醒
判断各种状态,特殊情况处理
第一部分代码如下,较为简单,获取锁成功之后直接返回

//对state进行cas修改操作,修改成功相当于获取锁,修改之后

state=1
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {return
}

第二部分自旋的代码如下

//开始等待时间
var waitStartTime int64
//这几个变量含义依次是:是否饥饿,是否唤醒,自旋次数,锁的当前状态
starving := false;awoke := false;iter := 0;old := m.state
//进入死循环,直到获得锁成功(获得锁成功就是有别的协程释放锁了)
for {//这个if的核心逻辑是判断:已经获得锁了并且不是饥饿模式 && 可以自旋,与cpu核数有关if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {//这个是判断:没有被唤醒 && 有排队等待的协程 && 尝试设置通知被唤醒if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 && atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {//说明上个协程此时已经unlock了,唤醒当前协程awoke = true}//自旋一段时间runtime_doSpin()//自选次数加1iter++old = m.statecontinue}
}

第三部分代码,判断各种状态,特殊情况处理

new := old//1:原协程已经unlock了,对new的修改为已锁
if old&mutexStarving == 0 { new |= mutexLocked
}
//2:这里是执行完自旋或者没执行自旋(原协程没有unlock)
if old&(mutexLocked|mutexStarving) != 0 {new += 1 << mutexWaiterShift //排队
}
//3:如果是饥饿模式,并且已锁的状态
if starving && old&mutexLocked != 0 {new |= mutexStarving //设置new为饥饿状态
}//4:上面的awoke被设置为true
if awoke {//当前协程被唤醒了,肯定不为0if new&mutexWoken == 0 {throw("sync: inconsistent mutex state")}//既然当前协程被唤醒了,重置唤醒标志为0new &^= mutexWoken
}

//修改state的值为new,但这里new的值会有四种情况,
//就是上面4个if情况对new做的修改,这一步获取锁成功

if atomic.CompareAndSwapInt32(&m.state, old, new) {if old&(mutexLocked|mutexStarving) == 0 {//这里代表的是正常模式获取锁成功break }//下面的代码是判断是否从饥饿模式恢复正常模式 queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime()}//进入阻塞状态  runtime_SemacquireMutex(&m.sema, queueLifo)//设置是否为饥饿模式,等待的时间大于1ms就是饥饿模式  starving=starving||runtime_nanotime()-waitStartTime> starvationThresholdNsold = m.state//如果当前锁是饥饿模式,但这个gorouting被唤醒if old&mutexStarving != 0 {if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}//减去当前锁的排队delta := int32(mutexLocked - 1<<mutexWaiterShift)if !starving || old>>mutexWaiterShift == 1 {//退出饥饿模式delta -= mutexStarving}//修改状态,终止  atomic.AddInt32(&m.state, delta)break}}    //设置被唤醒  awoke = trueiter = 0
} else {old = m.state
}

Lock的源码我们弄明白了,那么Unlock呢,大家看代码的时候最好Lock和Unlock结合一起来看,因为他们是对同一变量state在操作

func (m *Mutex) Unlock() {//释放锁new := atomic.AddInt32(&m.state, -mutexLocked)if (new+mutexLocked)&mutexLocked == 0 {throw("sync: unlock of unlocked mutex")}//判断当前锁是否饥饿模式,==0代表不是if new&mutexStarving == 0 {old := newfor {//如果没有未排队的协程 或者 有已经被唤醒,得到锁或饥饿的协程,则直接返回if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {return}//唤醒其它协程new = (old - 1<<mutexWaiterShift) | mutexWokenif atomic.CompareAndSwapInt32(&m.state, old, new) {runtime_Semrelease(&m.sema, false)return}old = m.state}} else {//释放信号量runtime_Semrelease(&m.sema, true)}
}

到这里整个Mutex的源码分析完成,可以看到Metux的源码并不是很复杂,只是各种位运算让开发人员难以直接观察到结果值,另外阅读源码前一定要先明白各个变量和常量的含义,不然读起来非常费劲。

Go中锁的那些姿势,估计你不知道相关推荐

  1. 有了TensorFlow.js,浏览器中也可以实时人体姿势估计

    翻译文章,内容有删减.原文地址:https://medium.com/tensorflow/real-time-human-pose-estimation-in-the-browser-with-te ...

  2. 论文笔记--3D Human Pose Estimation with Spatial and Temporal Transformers(用空间和时间变换器进行三维人体姿势估计)

    用空间和时间变换器进行三维人体姿势估计 摘要   Transformer架构已经成为自然语言处理中的首选模型,现在正被引入计算机视觉任务中,如图像分类.物体检测和语义分割.然而,在人类姿势估计领域,卷 ...

  3. OpenCV中的姿势估计及3D效果(3D坐标轴,3D立方体)绘制

    OpenCV中的姿势估计及3D效果(3D坐标轴,3D立方体)绘制 1. 效果图 2. 原理 3. 源码 3.1 姿态估计后绘制3D坐标轴 3.2 姿态估计后绘制立方体 参考 这篇博客将延续上一篇博客: ...

  4. 文献阅读-ICRA2020-从单眼内窥镜图像中对手术机器人器械的柄姿势估计

    文章序号.所属单元及链接:1630-Computer Vision for Medical Robots 一作所属单位:University of Tokyo 读后体会:以我浅薄的学术认知来看这篇论文 ...

  5. 无需3D运动数据训练,最新人体姿势估计方法达到SOTA | CVPR 2020

    作者 | Muhammed Kocabas 译者 | 刘畅 出品 | AI科技大本营(ID:rgznai100) 人体的运动对于理解人的行为是非常重要的.尽管目前已经在单图像3D姿势和动作估计方面取得 ...

  6. 基于 OpenCV 和 OpenPose 的棒球挥杆人体姿势估计

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 介绍 人体姿态估计是一个非常有趣的领域,如果我们能够将诸如棒球摆动 ...

  7. 使用姿势估计进行跌倒检测

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 所有目标检测已成为动作识别研究的重要垫脚石,即训练AI对行走和坐下 ...

  8. 深度学习的实时面部姿势估计研究

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 面部识别是深度学习的蓬勃发展的应用.从电话到机场摄像头,无论是在商 ...

  9. HybridPose:混合表示下的6D对象姿势估计

    论文题目:Single-Stage 6D Object Pose Estimation 论文地址:在公众号「3D视觉工坊」,后台回复「6D对象姿势估计」,即可直接下载. 摘要: 最新的6D姿态估计框架 ...

最新文章

  1. 监控操作系统和服务器,监控操作系统和服务器
  2. 解决idea中maven项目无法读取src/main/java目录下面的配置文件问题
  3. SPOJ 375 query on a tree 树链剖分
  4. 螺丝刀还能悬浮起来?
  5. [android] 手机卫士设置向导页面
  6. bootstrap table传回的数据后端怎么获取_基于 Spring Cloud 开发的分布式系统,遇到爬虫、接口盗刷怎么办
  7. 《Flutter 从0到1构建大前端应用》读后感—第4章【事件处理】
  8. 百度编辑器上传图片配置php,谁配置过百度编辑器ueditor1.4.3的图片上传路径?
  9. GameofMir__游戏Mod制作【1】
  10. 并行学习框架下基于GAN的城市道路网短时路段交通预测方法
  11. RFC2544吞吐量测试详细步骤-Renix软件操作演示
  12. hexo d上传报错鉴权失败
  13. iOS8 UIApplicationOpenSettingsURLString
  14. 小程序 实名信息_公告丨DUNK PLUS 小程序实名认证开启
  15. 百度网盘批量上传文件工具
  16. 智能学习 | MATLAB实现Bee-CNN蜜蜂算法优化卷积神经网络图像分类预测
  17. 华为机试题python版节选(基础编程题)
  18. 如何阅读一本专业书?
  19. mac 重命名文件快捷键
  20. 图灵奖得主长文报告:是什么开启了计算机架构的新黄金十年?

热门文章

  1. 深圳仁医堂皮肤医学研究:每天补水,为什么脸却越来越缺水
  2. 注解和反射12.动态创建对象执行方法
  3. 公众号文章用哪个网站制作最简单?
  4. Zemax光学设计(十五) —— 三片摄影物镜(1)
  5. 如何构建反汇编代码?
  6. 计算机与交通工程论文,交通信息与安全期刊_交通核心期刊_交通领域sci期刊
  7. SpringBoot 微信小程序 二手汽车交易系统 二手手机交易系统
  8. 非常简单的感染程序源代码
  9. 除了innerHTML,还有什么更好的方法
  10. 元器件降额规范(第二部分)持续更新