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解锁分为两个步骤

  1. 将Locked置为0
  2. 释放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 互斥锁实现原理的更多相关文章

  1. python网络编程之互斥锁

    标签(空格分隔): 互斥锁 进程之间的数据不共享,但是共享同一套文件系统,所以访问同一个文件,或者同一个打印终端,是没有问题的,而共享带来的问题就是竞争,竞争带来的结果就是错乱,如下: #并发运行,效 ...

  2. python互斥锁

    互斥锁 进程之间数据隔离, 但是多个进程可以共享同一块数据,比如共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,如下 from mu ...

  3. 4 并发编程-(进程)-守护进程&互斥锁

    一.守护进程 主进程创建子进程,然后将该进程设置成守护自己的进程,守护进程就好比崇祯皇帝身边的老太监,崇祯皇帝已死老太监就跟着殉葬了. 关于守护进程需要强调两点: 其一:守护进程会在主进程代码执行结束 ...

  4. 4-[多进程]-互斥锁、Queue队列、生产者消费者

    1.互斥锁 (1)为什么需要互斥锁 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如下 #并发运行,效率 ...

  5. 并发编程 - 线程 - 1.互斥锁/2.GIL解释器锁/3.死锁与递归锁/4.信号量/5.Event事件/6.定时器

    1.互斥锁: 原理:将并行变成串行 精髓:局部串行,只针对共享数据修改 保护不同的数据就应该用不用的锁 from threading import Thread, Lock import time n ...

  6. 互斥锁与join

    三 互斥锁与join 使用join可以将并发变成串行,互斥锁的原理也是将并发变成穿行,那我们直接使用join就可以了啊,为何还要互斥锁,说到这里我赶紧试了一下 #把文件db.txt的内容重置为:{&q ...

  7. python并发编程之多进程1--(互斥锁与进程间的通信)

    一.互斥锁 进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理. 注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行 ...

  8. python并发编程之多进程1互斥锁与进程间的通信

    一.互斥锁 进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理. 注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行 ...

  9. liteos互斥锁(七)

    1. 概述 1.1 基本概念 互斥锁又称互斥型信号量,是一种特殊的二值性信号量,用于实现对共享资源的独占式处理. 任意时刻互斥锁的状态只有两种,开锁或闭锁.当有任务持有时,互斥锁处于闭锁状态,这个任务 ...

随机推荐

  1. Android WebView优化

    1.展示webview的activity可以另开一个进程,这样就能和我们app的主进程分开了,即使webview产生了oom崩溃等问题也不会影响到主程序,如何实现呢,其实很简单,在androidman ...

  2. winform控件拖动

    示例代码 using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Form ...

  3. python3 requests的content和text方法

    text返回的是Unicode型的数据 content返回的是是二进制的数据. 也就是说,如果你想取文本,可以通过r.text. 如果想取图片,文件,则可以通过r.content >>&g ...

  4. mongodb中oplogsize大小设置

    1 oplogsize大小配置: 2 mongodb副本集构建的高可用方案,最少需要三个节点,一个主节点master,一个从节点slave,一个选举仲裁节点arbiter.当主节点奔溃的时候,仲裁节点 ...

  5. 执行df hang住

    突然有一天发现df执行卡住了,一直不显示结果. $ df -h Filesystem Size Used Avail Use% Mounted on /dev/sda3 221G 100G 121G ...

  6. Nginx同server配置下配置多个localhost路由地址

    nginx多页面路由配置,进入 nginx/conf/nginx.conf: http { ...... server { listen 80; server_name localhost; loca ...

  7. System.arraycopy()的用法?

    1.使用方法 public void arr(Object arr1, int x, Object arr2, int y, int length) arr1 : 源数组; x: 需要从源数组要复制的 ...

  8. freeswitch对接电信线路VOLTE视频通话

    在public.xml上设置视频编码: <action application="export" data="nolocal:absolute_codec_stri ...

  9. .NET 20周年软件趋势随想

    从2000年微软启动.NET战略时,我还是一位大学生,当年著名的黑客Miguel de Icaza , Miguel 为了寻找GNOME项目开发框架经过充分的调研启动了一个志存高远的项目:Mono,一 ...

  10. JAVA多线程学习十二 - Semaphere同步工具

    java 中Semaphere可类比操作系统信号量,硬件资源如IO.内存.磁盘等都是有固定量的,多个程序需要竞争这些资源,没有资源就需要被挂起. 一.类和方法摘要 构造函数: public Semap ...