go 互斥锁实现原理
go 互斥锁的实现
go中通过mutex来实现对互斥资源的锁定
1. mutex的数据结构
1.1 mutex结构体,抢锁解锁原理
go type Mutex struce{ state int32 sema uint32 }
- state表示互斥锁的状态,比如是否被锁定
- sema表示信号量,协程阻塞等待该信号量来唤醒协程,解锁的协程释放该信号量来唤醒阻塞的协程
下图展示了mutex的内存布局
- Locked:表示mutex是否已经锁定。1:锁定。 0:没有锁定
- Waiter:表示阻塞等待锁的协程个数,协程解锁时根据此值来判断是否需要释放信号量
- Starving:表示该mutex是否处于饥饿状态。0:没有饥饿。1:饥饿,表示有协程已经阻塞超过1ms
- Woken:表示是否有协程已被唤醒,0:没有协程唤醒。1:有协程唤醒,正在加锁过程中
协程之间抢锁的过程实际上是给Locked赋值1的过程,能给Locked赋值为1,表示抢锁成功,抢不到锁就阻塞等待sema信号量来唤醒加锁
1.2 mutex方法
Mutex对外提供两个方法
- Lock():加锁方法
- Unlock():解锁方法
2. 加解锁过程
2.1 简单加锁
假定当前只有一个协程在加锁,没有其他协程干扰,加锁过程如下
加锁过程会去判断Locked是否为1,如果是0则把Locked置为1,表示加锁成功,其他状态位不会发生变化
2.2 加锁被阻塞
假定加锁时,锁被其他协程占用,那么加锁过程如下:
如果协程对一个已经被占用的协程加锁时,Waiter计数器会增加1,此时B将会阻塞,直到Locked变为0后才会唤醒
2.3 简单解锁
假定解锁时,没有其他协程阻塞,那么解锁过程如下
由于此时watier值为0,表示没有其他协程在等待,所以无须释放信号量,只要把Locked置为0即可
2.4 解锁并释放协程
假定解锁过程,有1个或多个协程阻塞,那么此时的解锁过程
协程A解锁分为两个步骤
- 将Locked置为0
- 释放sema信号量,唤醒协程B,并将waiter减1
此时Locked为0,协程B收到信号量,将Locked置为1,B获得锁
3. 自旋过程
加锁时,如果当前Locked位为1, 说明该锁被其他协程占用,但尝试加锁的协程并不会马上转为阻塞状态,而是会持续的检测Locked位是否为0,这个过程称为自旋。
自旋的过程很短,如果在自旋过程中发现锁被释放,那么该协程会立即获得锁,被唤醒的协程会继续阻塞
自旋的好处是,当加锁失败时,不必立即转入阻塞,有一定机会获得锁,避免了协程之间的切换
3.1 什么是自旋
自旋对应CPU指令"PAUSE"(暂停,停顿),CPU对该指令什么都不做,相当于CPU空转,对程序而要相当于sleep了一段时间,该时间非常短,当前为30个时钟周期
自旋过程会持续检测Locked是否为0,它不同于sleep,不需要协程转为睡眠状态
3.2 自旋条件
- 自旋次数要足够小,通常为4,即自旋最多4次
- CPU核数要大于1,否则自旋没有意义,因为此时不可能有其他协程释放锁
- 协程调度机制中的Process数量要大于1,比如使用GOMAXPROCS()将处理器设置为1就不能启用自旋
- 协程调度机制中的可运行队列必须为空,否则会延迟协程调度
3.3 自旋的优势
自旋的优势是更充分的利用了CPU,避免了协程切换。因为当前申请加锁的协程获得了CPU,如果通过短时间自旋就可以获得锁,那么就可以直接运行,而不用阻塞并切换协程
3.4 自旋的问题
如果自旋过程中获得了锁,那么之前阻塞的协程就无法获得锁。如果等待加锁的协程特别多,而都在自旋过程中获得了锁,那么之前阻塞的协程就将一直阻塞。
为了解决这个问题,在1.8的版本之后增加了一个状态Starving。在这个状态下不会自旋,一定会有一个协程被唤醒并加锁
4. Mutex模式
现在我们看下Starving位的作用
每个Mutex都有两个模式,称为normal和Starving。
4.1 Normal模式
默认情况下都是Normal模式
当一个协程加锁失败时,不会立即转入等待状态,而是判断是否满足自旋条件,如果满足,则自旋来等待锁
4.2 Starving模式
自旋模式抢到锁,表示有协程释放了锁,我们知道释放锁时,如果waiter>0,即有阻塞等待的协程,会释放信号量来唤醒协程,当协程被唤醒后,发现Locked=1,锁又被抢占,则又会阻塞,但在阻塞前会判断自上次阻塞到本次阻塞经历了多长时间,如果超过1ms的话,会将Mutex标记为"饥饿"模式,然后再阻塞
在饥饿模式下,不会启动自旋,如果有协程释放锁,那么一定会唤醒一个协程,被唤醒的协程会获得锁,同时会把waiter减1.
5. Woken状态
Woken状态作用于加锁和解锁的过程中,如果一个协程正在解锁,另一个协程在自旋等待加锁,那么会把Woken状态置为1,通知解锁的协程不用释放信号量。
6. 为什么重复解锁要panic
unlock()过程分为将locked置为0,然后判断waiter是否大于0,如果大于0就释放信号量
如果多次unlock(),则可能会唤醒多个协程,多个协程唤醒后会继续在Lock()的逻辑里抢锁,势必会增加Lock()实现的复杂度,也会引起不必要的协程切换。
go 互斥锁实现原理的更多相关文章
- python网络编程之互斥锁
标签(空格分隔): 互斥锁 进程之间的数据不共享,但是共享同一套文件系统,所以访问同一个文件,或者同一个打印终端,是没有问题的,而共享带来的问题就是竞争,竞争带来的结果就是错乱,如下: #并发运行,效 ...
- python互斥锁
互斥锁 进程之间数据隔离, 但是多个进程可以共享同一块数据,比如共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,如下 from mu ...
- 4 并发编程-(进程)-守护进程&互斥锁
一.守护进程 主进程创建子进程,然后将该进程设置成守护自己的进程,守护进程就好比崇祯皇帝身边的老太监,崇祯皇帝已死老太监就跟着殉葬了. 关于守护进程需要强调两点: 其一:守护进程会在主进程代码执行结束 ...
- 4-[多进程]-互斥锁、Queue队列、生产者消费者
1.互斥锁 (1)为什么需要互斥锁 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如下 #并发运行,效率 ...
- 并发编程 - 线程 - 1.互斥锁/2.GIL解释器锁/3.死锁与递归锁/4.信号量/5.Event事件/6.定时器
1.互斥锁: 原理:将并行变成串行 精髓:局部串行,只针对共享数据修改 保护不同的数据就应该用不用的锁 from threading import Thread, Lock import time n ...
- 互斥锁与join
三 互斥锁与join 使用join可以将并发变成串行,互斥锁的原理也是将并发变成穿行,那我们直接使用join就可以了啊,为何还要互斥锁,说到这里我赶紧试了一下 #把文件db.txt的内容重置为:{&q ...
- python并发编程之多进程1--(互斥锁与进程间的通信)
一.互斥锁 进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理. 注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行 ...
- python并发编程之多进程1互斥锁与进程间的通信
一.互斥锁 进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理. 注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行 ...
- liteos互斥锁(七)
1. 概述 1.1 基本概念 互斥锁又称互斥型信号量,是一种特殊的二值性信号量,用于实现对共享资源的独占式处理. 任意时刻互斥锁的状态只有两种,开锁或闭锁.当有任务持有时,互斥锁处于闭锁状态,这个任务 ...
随机推荐
- 基于华为云服务器的FTP站点搭建
前言 主要介绍了华为云上如何使用弹性云服务器的Linux实例使用vsftpd软件搭建FTP站点.vsftpd全称是"very secure FTP daemon",是一款在Linu ...
- Go环境配置和GoModule
Linux相关 Linux常用操作 mkdir directory --创建文件夹 vi file --创建文件,再关闭vim rm file --删除文件 rm -rf directory --递归 ...
- MVC框架---转
浅析MVC模式 摘要:MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面 ...
- 集合框架-ArrayList集合存储自定义对象
1 package cn.itcast.p3.arraylist.test; 2 3 import java.util.ArrayList; 4 import java.util.Iterator; ...
- JVM之Java内存区域
JVM之Java内存区域 世界上并没有完美的程序,但我们并不因此而沮丧,因为写程序本来就是一个不断追求完美的过程. 一.JAVA内存区域 谈及JAVA虚拟机运行时数据区域就不得不祭出这张经典的图了: ...
- CSS之创意hover效果
一.发送效果 HTML <div id="send-btn"> <button> // 这里是一个svg的占位 Send </button> & ...
- django之集成阿里云通信(发送手机短信验证码)
python3 + django2.0 集成 "阿里云通信" 服务: (SDK文档地址:https://help.aliyun.com/document_detail/55491. ...
- eureka的简单介绍,eureka单节点版的实现?eureka的自我保护?eureka的AP性,和CP性?
注意!!! 这是对上一篇博客 springcloud的延续,整个项目的搭建,来源与上一篇博客.一.什么是eureka? // eureka是一个注册中心,实现了dubbo中zookeeper的效果! ...
- 【然天一】随机读写(4k)百盘天梯
随机读写适用于大量小文件的读写,是最贴近办公和编程的使用场景.现在很多硬盘厂商只宣传它们的连续读写(Seq),但除了游戏和视频剪辑场景之外并没有什么卵用. 总结一下: 傲腾秒杀全部 NAND SLC ...
- 用Json给表单赋值
$.extend({ setForm :function(frm,jsonValue) { var obj=$(frm); $.each(jsonValue, function (name, ival ...