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. Microsoft Store 桌面应用发布流程(一)之打包应用

    这篇博客主要是介绍桌面应用打包的流程,应用发布流程请看 Microsoft Store 桌面应用发布流程(二)之提交应用 1. 创建打包项目 打开现有的桌面应用项目.选择解决方案项目,右键选择 添加新 ...

  2. 515. Find Largest Value in Each Tree Row

    You need to find the largest value in each row of a binary tree. Example: Input: 1 / \ 3 2 / \ \ 5 3 ...

  3. 使用Xamarin开发移动应用示例——数独游戏(一)项目的创建与调试

    最近项目中需要移动客户端,由于团队基本上使用.Net产品线,所以决定使用Xmarin进行开发,这样技术路线统一,便于后期维护.官网上是这样介绍的" Xamarin 允许你使用 .NET 代码 ...

  4. String类-intern方法

    1 package cn.itcast.p1.string.demo; 2 3 class StringObjectDemo { 4 public static void main(String[] ...

  5. Postman 支持 gRPC 了!继续领先 ~

    最近国产API管理工具比较热,几款产品在API管理层面做得也都还不错,但主要还是对HTTP相关的API管理,毕竟这类API的应用目前还是最为广泛的.但显然,还有不少其他应用场景目前没有覆盖到,DD在之 ...

  6. 关于笨蛋式病毒创作(CMD式)C++

    ​ 对于病毒创作,有好多种原因:有想装逼的,想盗取信息的...... 任何理由千奇百出,有的在下面评论出来. 对于病毒创作,我可是既会有不会,只会乱写,回头一看,全错了. 我今天写的这篇博客就是面向于 ...

  7. Java多线程专题4: 锁的实现基础 AQS

    合集目录 Java多线程专题4: 锁的实现基础 AQS 对 AQS(AbstractQueuedSynchronizer)的理解 Provides a framework for implementi ...

  8. APC 篇—— APC 执行

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  9. CKKS Part5: CKKS的重缩放

    本文翻译于 CKKS EXPLAINED, PART 5: RESCALING,主要介绍CKKS方案中最重要的技术- rescaling,重缩放技术 介绍 在CKKS的前一篇文章 CKKS Part4 ...

  10. 「YNOI2016」自己的发明

    「YNOI2016」自己的发明 不换根 基本的莫队吧... 子树直接转到dfs序上. 其余部分可以见 「SNOI2017」一个简单的询问. 换根 根root,查询x,分3种: root不在x子树内,按 ...