计数信号量是一种锁,它可以让用户限制一项资源最多能够同时被多少个进程访问,通常用于限定能够同时使用的资源数量。之前所了解到的分布式锁可以看做是只能被一个进程访问的信号量。 计数信号量和其他种类的锁一样,都需要被获取和释放。客户端首先需要获取信号量,然后执行操作,最后释放信号量。计数信号量和其他锁的区别在于,当客户端获取锁失败的时候,客户端通常会选择进行等待;而当客户端获取计数信号量失败的时候,客户端通常会选择立即返回失败结果。

基本的计数信号量

将多个信号量的持有者的信息都存储到同一个有序集合里面,程序将为每个尝试获取信号量的进程生成一个唯一标识符(比如UUID),并将这个标识符用作有序集合的成员,而成员对应的分值则是进程尝试获取信号量时的Unix时间戳。接着进程会检查自己的标识符在有序集合中的排名,如果排名低于可获取的信号量总数(成员的排名从0开始计算),那么表示进程成功地取得了信号量。反之,则表示进程未能取得信号量,它必须从有序集合里面移除自己的标识符。为了处理过期的信号量,程序在将标识符添加到有序集合之前,会先清理有序集合中所有时间戳大于超时数值(timeout number value)的标识符。

获取信号量的步骤:

  1. 清理有序集合中所有已过期的UUID(时间戳 <= 当前时间戳 - 过期时间);
  2. 生成UUID,使用当前时间戳作为分值,将UUID添加到有序集合里面;
  3. 检查刚刚的UUID的排名,若排名低于可获取的信号量总数(成员排名从0开始计算),那么表示成功获取了信号量;若排名等于或高于可获取的信号量总数,那么未获取成功,需要将刚刚的UUID移除。释放信号量时直接从有序集合中删除UUID即可。若返回值为1 ,则表明成功手动释放;若返回值为0 ,则表明已经由于过期而自动释放。

缺点:

  1. 所有信号量的过期时间都需要一样(为了方便删除过期的UUID);
  2. 不公平,依赖系统时间,当多机环境下,A 的系统时间比B的系统时间快10ms ,那么当 A 取得了最后一个信号量的时候,B只要在10ms内尝试获取信号量,那么就会造成B获取了不存在的信号量,导致获取的信号量超过了信号量的总数;还可能造成信号量提早被其他系统的获取请求释放。

每当锁或者信号量因为系统时钟的细微不同而导致锁的获取结果出现剧烈变化是,这个锁或者信号量就是不公平的(unfair),不公平的锁和信号量可能会导致客户端永远也无法取得它原本应该得到的锁或者信号量。

公平信号量

当各个系统的系统时间并不完全相同的时候,基本信号量就会出现问题:系统时钟较慢的系统上运行的客户端,将能够偷走系统时钟较快的系统上运行的客户端已经取得的信号量,导致信号量变得不公平。我们需要减少不正确的系统时间对信号量获取操作带来的影响,是的只要各个系统的系统时间相差不超过1秒,就不会引起信号量被偷或者信号量提早过期。
为了尽可能地减少系统时间不一致带来的问题,我们需要给信号量实现添加一个计数器以及一个新的有序集合。其中,计数器通过持续地执行自增操作,每次发出获取请求前先对其自增,创建出一种类似于计时器(timer)的机制,确保最先对计数器执行自增操作的客户端能过获得信号量。另外,为了满足“最先对计数器执行自增操作的客户端能够获得信号量”这一要求,程序会将计数器生成的值用作分值,将对应的UUID存储到一个“信号量拥有者”的有序集合里面,然后检查客户端生成的标识符在有序集合中的排名来判断客户端是否取得了信号量。
即原本的有序集合仅用来查找并删除过期的UUID ,新的有序集合用来获取排名判断请求是否成功获取到信号量。同时为了保持新的有序集合能及时删过期的UUID ,在原本的有序集合执行完删除操作后,还要使用 ZINTERSTORE命令,保留仅在原本有序集合中出现的UUID (ZINTERSTORE count_set 2 count_set time_set WEIGHTS 1 0)。
注意:若信号量获取失败,则需要及时删除本次插入的无用数据。
上述方法能在一定程度上解决信号量获取数超过信号量总数的问题,但删除过期UUID的地方还是依赖本地时间,所以尽量保证各个主机的系统时间差距要足够小,仍然需要控制在一两秒之内,从而避免信号量过早释放或者太晚释放。

做到与系统时间无关
去除原本的有序集合,仅留下计数器和计数值作为分值的有序集合,并对于每个UUID都设置一个有过期时间的键,每次移除前,遍历有序集合,并查询其是否过期,并从有序集合中删除所有已过期的UUID 。
这样做不仅能完全达到与系统时间无关,还不会存在信号量获取数超过信号量总数的问题,且能够实现单个获取的信号量能有不同的过期时间,也一定程度上降低了时间复杂度,不过会增加客户端与 Redis 服务器之间的交互次数。

刷新信号量

信号量使用者可能在过期时间内无法处理完请求,此时就需要续约,延长过期时间。由于公平的计数信号量已将时间有序集合和计数有序集合分开,所以只需要在时间有序集合中对UUID执行ZADD即可,若执行失败,则已过期自动释放。
而对于上述“做到与系统时间无关”的方法,有两种方法可以进行续约:

  1. 使用Lua脚本保证原子性;
  2. 先读取过期时间,未过期,再使用带XX选项的SET命令设置新的过期时间(需要加上原有的过期时间),返回成功则续约成功,否则续约失败;已过期则续约失败。

消除竞争条件

当两个进程A和B都在尝试获取剩余的一个信号量时,即使A首先对计数器执行了自增操作,但只要B能够抢先将自己的标识符(UUID)添加到计数有序集合中,并检查标识符(UUID)的排名,那么B就可以成功获取信号量。之后A再将自己的标识符(UUID)添加到有序集合里,并检查标识符(UUID)排名,那么A也可以成功获取信号量,最终导致获取的信号量多于信号量总数,而B只有在尝试释放信号量或者尝试刷新信号量的时候才会觉察到这一点。
将系统时钟用作获取锁的手段提高了这类竞争条件出现的可能性,导致信号量持有者的数量与预期的还要多,多出的信号量数量与各个系统时钟之间的差异有关——差异越大,出现额外信号量持有者的可能性也就越大。虽然引入计数器和信号量持有者有序集合可以移除系统时钟这一不确定因素,并降低竞争条件出现的几率,但由于执行信号量获取操作需要客户端和服务端进行多次通信,所以竞争条件还是有可能会发生。
为了消除获取信号量时所有可能出现的竞争条件,构建一个正确的计数信号量,我们需要用到前面完成的带有超时功能的分布式锁。在想要获取信号量时,首先尝试获取分布式锁,若获取锁成功,则继续执行获取信号量的操作;若获取锁失败,那么获取信号量也失败。

不同计数信号量的使用场景:

  1. 基本的计数信号量:对于多机系统时间的差异不关心,也不需要对信号量进行刷新,并且能够接受信号量的数量偶尔超过限制;
  2. 公平的计数信号量:对于多机系统时间的差异不是非常敏感,但仍然能够接受信号量但数量偶尔超过限制;
  3. 正确的计数信号量:希望信号量一直都具有正确的行为,那么可以使用带锁的信号量实现来保证正确性。

Redis计数信号量相关推荐

  1. xsemaphoretake返回_FreeRTOS 计数信号量

    本章节开始讲解 FreeRTOS 任务间的同步和资源共享机制,计数信号量. FreeRTOS 中计数信号量的源码实现是基于消息队列实现的. 信号量的概念及其作用 信号量(semaphores)是 20 ...

  2. 计数信号量的删除与状态查询

    文章目录 1 计数信号量的删除与状态查询 1 计数信号量的删除与状态查询 信号量的删除: 信号量的状态查询: 参考资料: [李述铜]从0到1自己动手写嵌入式操作系统

  3. 计数信号量的获取与释放

    文章目录 1 计数信号量的获取与释放 1.1 设计实现 1 计数信号量的获取与释放 1.1 设计实现 信号量的wait: 信号量的notify: 信号量的无等待获取: /*************** ...

  4. 计数信号量的原理与创建

    文章目录 1 计数信号量的原理与创建 1.1 概述 1.2 设计原理 1.3 设计实现 1 计数信号量的原理与创建 1.1 概述 信号量简而言之就是一个带事件控制的计数器,在其上定义了三个操作: 可以 ...

  5. 代码示例:使用redis计数来控制单位时间内对某接口的访问量

    控制单位时间内接口的访问量 使用redis计数来控制单位时间内对某接口的访问量,防止刷验证码接口之类的. 使用自定义注解的方式,在需要被限制访问频率的方法上加注解即可控制. 看实现方式,基于sprin ...

  6. 二进制信号量,互斥信号和计数信号量的区别

    VxWorks的信号量机制分析 VxWorks信号量是提供任务间通信.同步和互斥的最优选择,提供任务间最快速的通信.也是提供任务间同步和互斥的主要手段.VxWorks提供3种信号量来解决不同的问题. ...

  7. FreeRTOS 计数信号量

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节开始讲解 FreeRTOS 任务间的同步和资源共享机制,计数信号量. FreeRTOS 中计数信号量的 ...

  8. Linux内核信号量:二值信号量/互斥信号量,计数信号量,读写信号量

    <semaphore信号量:一个简单的示例程序>用户态程序 目录 概念 应用场景 使用方法 内核信号量的构成 信号量的API 初始化 PV操作 获取信号量(P) 释放内核信号量(V) 补充 ...

  9. RTOS原理与实现06:计数信号量实现

    目录 1. 计数信号量的原理与创建 1.1 概述 1.2 设计原理 1.3 设计实现 1.3.1 定义信号量 1.3.2 添加tSemInit函数 2. 计数信号量的获取与释放 2.1 设计原理 2. ...

最新文章

  1. solaris系统的网卡配置
  2. Code128 Fontware条码控件介绍
  3. 云计算之路-试用Azure:数据库备份压缩文件在虚拟机上的恢复速度测试
  4. springboot项目中的注解 启动项目的方式 解决spring的bean.xml配置不生效 spring的基础JDBC配置
  5. 用Python实现磁盘IO操作全攻略,让数据流动起来!
  6. Android 手机常见问答
  7. Codeforece E. Anton and Permutation
  8. messageBox的样式修改
  9. 【工业智能】天泽智云带你解读工业智能热门科技 – 大数据、工业互联网、云计算、边缘计算、CPS
  10. 时间序列分析和预测(含实例及代码)
  11. 计算机图像处理入门基础知识——色域,色度图,色彩空间和色彩管理
  12. 各种坐标系下的散度、梯度、旋度公式
  13. 郭天祥的10天学会51单片机_第四节
  14. STM32F429实现USB通过IAP在线升级
  15. Java代码给csv文件加水印_如何给文件加上水印?
  16. Nature综述:2万字带你系统入门鸟枪法宏基因组实验和分析
  17. DAVINCI DM3730开发攻略——开发环境篇
  18. 孤尽T31项目第6天-Java项目工程结构规约
  19. python中plotly绘制树地图_聚类分析python画树状图--Plotly(dendrogram)用法解析 - 人人都是架构师...
  20. 【CSDN竞赛第25期】赢热门图书《千脑智能》和定制周边

热门文章

  1. 值得一听,改变人生的21个演讲
  2. JAVA使用LineNumberReader获取文件的行数
  3. [好好笑] 万峰语录3月26日
  4. 35.驱动--MTD子系统
  5. 剖面式细管温度传感器-农业物联网传感器-配套农业气象站使用
  6. [GIS算法] 判断两线段是否相交的四种方案(快速排斥+跨立实验、参数方程求解、凸多边形、点在线的哪一侧)-附C语言实现
  7. 高数下|偏导数|高数叔|手写笔记
  8. PYTHON笔记——猎人过河
  9. 我把废旧 Android 手机改造成了 Linux 服务器,Android开发学习视频
  10. Mybatis的CRUD 增删改查