当前位置: 首页 > news >正文

并发编程系列-Semaphore

Semaphore,如今通常被翻译为"信号量",过去也曾被翻译为"信号灯",因为类似于现实生活中的红绿灯,车辆是否能通行取决于是否是绿灯。同样,在编程世界中,线程是否能执行取决于信号量是否允许。

信号量是由著名的计算机科学家迪杰斯特拉(Dijkstra)于1965年提出的,直到1980年管程被提出,它一直是并发编程领域的主导方法。如今几乎所有支持并发编程的语言都支持信号量机制,因此掌握信号量仍然非常必要。

下面我们首先介绍信号量模型,然后介绍如何运用信号量,最后使用信号量来实现一个流量控制器。

信号量模型

信号量模型还是很简单的,可以简单概括为: 一个计数器,一个等待队列,三个方法。在信号量模型里,计数器和等待队列对外是透明的,所以只能通过信号量模型提供的三个方法来访问它们,这三个方法分别是:init()、down()和up()。你可以结合下图来形象化地理解。

alt

这三个操作的具体意义如下所示。

  • 初始化(init()):设定计数器的起始值。
  • 减少(down()):将计数器的值减1;如果此时计数器的值小于0,则当前线程会被阻塞,否则当前线程可以继续执行。
  • 增加(up()):将计数器的值加1;如果此时计数器的值小于或等于0,则唤醒等待队列中的一个线程,并将其从等待队列中移除。

上述提到的init()、down()和up()三个操作都是原子的,并且这种原子性由信号量模型的实现方保证。在Java SDK中,信号量模型由java.util.concurrent.Semaphore类实现,Semaphore类能够确保这三个操作都是原子操作。

如果你觉得上面的描述有些复杂,可以参考下面的代码来理解信号量模型的实现。

class Semaphore{
  // 计数器
  int count;
  // 等待队列
  Queue queue;
  // 初始化操作
  Semaphore(int c){
    this.count=c;
  }
  //
  void down(){
    this.count--;
    if(this.count<0){
      //将当前线程插入等待队列
      //阻塞当前线程
    }
  }
  void up(){
    this.count++;
    if(this.count<=0) {
      //移除等待队列中的某个线程T
      //唤醒线程T
    }
  }
}

这里再插一句,信号量模型里面,down()、up()这两个操作历史上最早称为P操作和V操作,所以信号量模型也被称为PV原语。另外,还有些人喜欢用semWait()和semSignal()来称呼它们,虽然叫法不同,但是语义都是相同的。在Java SDK并发包里,down()和up()对应的则是acquire()和release()。

如何使用信号量

通过前文,你应该会发现信号量的模型还是非常简单的,那具体应该如何使用呢?其实你可以想象一下红绿灯。在十字路口,红绿灯可以控制交通流量,这得益于一个重要的规则:车辆在通过路口前必须检查是否为绿灯,只有绿灯才能通行。这个规则与我们之前提到的锁规则很相似,不是吗?

实际上,信号量的使用也是类似的。让我们继续用累加器的例子来说明信号量的使用吧。在累加器的例子中,count += 1操作是一个临界区,只允许一个线程执行,也就是说需要保证互斥性。那么,在这种情况下,如何使用信号量来控制呢?

实际上非常简单,就像我们使用互斥锁一样,只需要在进入临界区之前执行一次down()操作,在退出临界区之前执行一次up()操作就可以了。下面是Java代码的示例,acquire()就是信号量中的down()操作,release()就是信号量中的up()操作。

static int count;
//初始化信号量
static final Semaphore s
    = new Semaphore(1);
//用信号量保证互斥
static void addOne() {
  s.acquire();
  try {
    count+=1;
  } finally {
    s.release();
  }
}

接下来我们再来分析一下,信号量是如何确保互斥性的。假设有两个线程T1和T2同时访问addOne()方法,当它们同时调用acquire()时,由于acquire()是一个原子操作,因此只能有一个线程(假设是T1)将信号量中的计数器减为0,而另一个线程(T2)将计数器减为-1。对于线程T1来说,信号量中计数器的值是0,大于等于0,所以线程T1会继续执行;对于线程T2来说,信号量中计数器的值是-1,小于0,根据信号量模型中对down()操作的描述,线程T2将被阻塞。因此,此时只有线程T1可以进入临界区并执行 count += 1;

当线程T1执行release()操作,也就是up()操作时,信号量中计数器的值是-1,经过加1后的值是0,小于等于0,根据信号量模型中对up()操作的描述,此时等待队列中的T2将被唤醒。于是,在T1执行完临界区代码之后,T2才有机会进入临界区执行,从而确保了互斥性。

快速实现一个限流器

上述的示例,我们通过使用信号量实现了一个简单的互斥锁功能。也许你会觉得奇怪,既然Java的SDK中已经提供了Lock,为什么还需要提供Semaphore呢?实际上,互斥锁只是Semaphore的一部分功能,而Semaphore还有一个Lock无法实现的功能,那就是:允许多个线程访问临界区

实际情况中确实存在这样的需求。比较常见的例子是我们在工作中遇到的各种资源池,比如连接池、对象池、线程池等等。其中,你可能对数据库连接池最为熟悉,在同一时刻,允许多个线程同时使用连接池,但每个连接在释放之前不允许其他线程使用。

实际上,不久前我在工作中也遇到了一个对象池的需求。对象池指的是一次性创建N个对象,然后所有线程重复利用这些对象,当然在对象释放之前不允许其他线程使用。对象池可以使用List来保存实例对象,这很简单。但关键在于限流器的设计,这里的限流指的是不允许超过N个线程同时进入临界区。那么如何快速实现这样的限流器呢?我立刻想到了使用信号量的解决方案。

在上面的例子中,信号量的计数器被设置为1,这个1表示只允许一个线程进入临界区。但是,如果我们将计数器的值设为对象池中对象的数量N,就可以完美解决对象池的限流问题了。下面是一个对象池的示例代码。

class ObjPool<T, R> {
  final List<T> pool;
  // 用信号量实现限流器
  final Semaphore sem;
  // 构造函数
  ObjPool(int size, T t){
    pool = new Vector<T>(){};
    for(int i=0; i<size; i++){
      pool.add(t);
    }
    sem = new Semaphore(size);
  }
  // 利用对象池的对象,调用func
  R exec(Function<T,R> func) {
    T t = null;
    sem.acquire();
    try {
      t = pool.remove(0);
      return func.apply(t);
    } finally {
      pool.add(t);
      sem.release();
    }
  }
}
// 创建对象池
ObjPool<Long, String> pool =
  new ObjPool<Long, String>(10, 2);
// 通过对象池获取t,之后执行
pool.exec(t -> {
    System.out.println(t);
    return t.toString();
});

我们使用一个List来存储对象实例,并使用Semaphore实现限流器。关键代码位于ObjPool的exec()方法中,这个方法实现了限流的功能。在这个方法中,我们首先调用acquire()方法(在finally块中会调用release()方法),假设对象池大小为10,信号量的计数器初始化为10,那么前10个线程调用acquire()方法后可以继续执行,相当于通过了信号灯,而其他线程将阻塞在acquire()方法上。通过了信号灯的线程,我们为每个线程分配一个对象t(通过pool.remove(0)实现),并执行一个回调函数func,该回调函数的参数正是之前分配的对象t;在执行完回调函数后,它们会释放对象(通过pool.add(t)实现),同时调用release()方法来更新信号量的计数器。如果此时信号量的计数器值小于等于0,说明有线程在等待,此时会自动唤醒等待的线程。

简而言之,使用信号量,我们可以轻松实现一个限流器,且使用起来非常简单。

总结

信号量在Java语言中的知名度相对较低,但在其他编程语言中却非常有名。Java在并发编程领域取得了很快的发展,重点支持的是管程模型。管程模型在理论上解决了信号量模型的一些不足之处,主要体现在易用性和工程化方面。例如,使用信号量来解决我们前面提到的阻塞队列问题比使用管程模型要复杂许多。如果你感兴趣的话,可以进行了解和尝试。

本文由 mdnice 多平台发布

相关文章:

并发编程系列-Semaphore

Semaphore&#xff0c;如今通常被翻译为"信号量"&#xff0c;过去也曾被翻译为"信号灯"&#xff0c;因为类似于现实生活中的红绿灯&#xff0c;车辆是否能通行取决于是否是绿灯。同样&#xff0c;在编程世界中&#xff0c;线程是否能执行取决于信号量是否允…...

3年 Android 开发的面试心经(后悔当初没有拿 N+1)

作者&#xff1a;勇闯天涯 当某人顺利通过大厂面试时&#xff0c;总会有人认为这是运气比较好罢了&#xff0c;但他们不曾得知对方之前受过多少苦和委屈&#xff0c;又付出了多少努力一步步去突破这些困境。正是因为他们的努力付出&#xff0c;在合适的时间与地点&#xff0c;用…...

【c语言】 -- 指针进阶

&#x1f4d5;博主介绍&#xff1a;目前大一正在学习c语言&#xff0c;数据结构&#xff0c;计算机网络。 c语言学习&#xff0c;是为了更好的学习其他的编程语言&#xff0c;C语言是母体语言&#xff0c;是人机交互接近底层的桥梁。 本章来学习指针进阶。 让我们开启c语言学习…...

软件压力测试对软件产品起到什么作用?

一、软件压力测试是什么? 软件压力测试是一种通过模拟正常使用环境中可能出现的大量用户和大数据量的情况&#xff0c;来评估软件系统在压力下的稳定性和性能表现的测试方法。在软件开发过程中&#xff0c;经常会遇到一些性能瓶颈和稳定性问题&#xff0c;而软件压力测试的作…...

Stephen Wolfram:那么…ChatGPT 在做什么,为什么它有效呢?

So … What Is ChatGPT Doing, and Why Does It Work? 那么…ChatGPT在做什么&#xff0c;为什么它有效呢&#xff1f; The basic concept of ChatGPT is at some level rather simple. Start from a huge sample of human-created text from the web, books, etc. Then train…...

机器学习基础(五)

决策树 决策树是一种预测模型,它代表着对象属属性与对象值之间的一种映射关系。树中的每个节点代表一个对象,分叉路径(或者叫树枝)则代表一个属性值。 决策树常用方法: 分类树分析,是一种监督学习,用于预计结果可能为离散类型。 回归树分析,用于预计结果为实数。 CART,…...

阿里云服务器安装WordPress网站教程基于CentOS系统

阿里云百科分享使用阿里云服务器安装WordPress博客网站教程&#xff0c;WordPress是使用PHP语言开发的博客平台&#xff0c;在支持PHP和MySQL数据库的服务器上&#xff0c;您可以用WordPress架设自己的网站&#xff0c;也可以用作内容管理系统&#xff08;CMS&#xff09;。本教…...

【100天精通python】Day37:GUI界面编程_PyQT从入门到实战(上)

目录 专栏导读 1 PyQt6 简介&#xff1a; 1.1 安装 PyQt6 和相关工具&#xff1a; 1.2 PyQt6 基础知识&#xff1a; 1.2.1 Qt 的基本概念和组件&#xff1a; 1.2.2 创建和使用 Qt 窗口、标签、按钮等基本组件 1.2.3 布局管理器&#xff1a;垂直布局、水平布局、网格布局…...

数据结构—散列表的查找

7.4散列表的查找 7.4.1散列表的基本概念 基本思想&#xff1a;记录的存储位置域关键字之间存在对应关系 ​ 对应关系——hash函数 ​ Loc&#xff08;i&#xff09; H&#xff08;keyi&#xff09; 如何查找&#xff1a; 根据散列函数 H(key) k 查找key9&#xff0c;则访…...

Expo项目 使用Native base UI库

装包&#xff1a; yarn add native-base expo install react-native-svg12.1.1 Index.js: import React from react import { View, Text } from react-native import useList from ./useList import { NativeBaseProvider, Button, Box } from native-base import styles f…...

74、75、76——tomcat项目实战

tomcat项目实战 tomcat 依赖 java运行环境,必须要有jre , 选择 jdk1.8 JvmPertest 千万不能用 kyj易捷支付 项目机器 选择 一台机器 ,安装jdk1.8的机器下载tomcat的包 上传到机器,解压tomcattomcat文件 bin文件夹: 启动文件 堆栈配置文件 catalina.sh JAVA_OPTS="-Xm…...

jmeter errstr :“unsupported field type for multipart.FileHeader“

在使用jmeter测试接口的时候&#xff0c;提示errstr :"unsupported field type for multipart.FileHeader"如图所示 这是因为我们 在HTTP信息头管理加content-type参数有问题 直接在HTTP请求中&#xff0c;勾选&#xff1a; use multipart/form-data for POST【中文…...

C#调用C++ DLL传参byte[]数组字节值大于127时会变为0x3f的问题解决

最近做了一个网络编程的DLL给C#调用&#xff0c;DLL中封装了一个TCP Client的函数接口&#xff0c;如下所示 //C TCP报文发送接口 int TcpClient_send(unsigned char* buffSend, unsigned int nLen) {unsigned char buff[1024];int len StringToHex(buffSend, buff);int nRet…...

【vue3+xlxs+xlsx-style-vite】vue3项目中使用xlsx插件实现Excel表格的导出和解析,已实现

在vue3项目中使用xlsx插件实现Excel表格的导出和解析 1、xlsx插件包官方 xlsx插件包官方 2、FileReader官方文档&#xff1a;FileReader官方文档 安装xlsx和xlsx-style-vite、file-saver npm install xlsx npm install xlsx-style-vite npm install file-saverpackage.json中查…...

Doris2.0时代的一些机遇和挑战!

300万字&#xff01;全网最全大数据学习面试社区等你来&#xff01; 上个周五的时候&#xff0c;Doris官宣了2.0版本&#xff0c;除了在性能上的大幅提升&#xff0c;还有一些特性需要大家特别关注。 根据官网的描述&#xff0c;Doris在下面领域都有了长足进步&#xff1a; 日志…...

Leetcode-每日一题【剑指 Offer 32 - I. 从上到下打印二叉树】

题目 从上到下打印出二叉树的每个节点&#xff0c;同一层的节点按照从左到右的顺序打印。 例如: 给定二叉树: [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回&#xff1a; [3,9,20,15,7] 提示&#xff1a; 节点总数 < 1000 解题思路 1.题目要求我们从…...

网神 SecGate 3600 防火墙任意文件上传漏洞复现

0x01 产品简介 网神SecGate3600下一代极速防火墙&#xff08;NSG系列&#xff09;是基于完全自主研发、经受市场检验的成熟稳定网神第三代SecOS操作系统 并且在专业防火墙、VPN、IPS的多年产品经验积累基础上精心研发的高性能下一代防火墙 专门为运营商、政府、军队、教育、大型…...

把独显塞回CPU,新核显能够媲美RTX 30、40系显卡了

上个月&#xff0c;AMD 发布了 Zen4 架构 R5 7600X 的无核显版 - 7500F 。 各种数据评测和玩家实际体验大家也已经看过了&#xff0c;说是变相降价一点不错。 原因也很简单&#xff0c;感谢 Intel 。 Jon Peddie Research 刚出炉报告显示&#xff0c;2023 第二季度 AMD 客户端…...

Python爬虫——scrapy_工作原理

引擎向spiders要url引擎把将要爬取的url给调度器调度器会将url生成的请求对象放入到指定的队列中从队列中出队一个请求引擎将请求交给下载器进行处理下载器发送请求获取互联网数据下载器将数据返回给引擎引擎将数据再次给到spidersspiders通过xpath解析该数据&#xff0c;得到数…...

gRPC vs REST:创建API的方法比较

本文对gRPC和REST的特征和区别进行了介绍&#xff0c;这可能是当今创建API最常用的两种方法。 文章目录 一、gRPC的介绍 二、什么是REST&#xff1f; 三、什么是gRPC? 四、gRPC和REST的比较 &#xff08;1&#xff09;底层HTTP协议 &#xff08;2&#xff09;支持的数据…...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

Python网页自动化Selenium中文文档

1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API&#xff0c;让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API&#xff0c;你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...