并发编程 - 线程同步(九)之信号量Semaphore
前面对自旋锁SpinLock进行了详细学习,今天我们将学习另一个种同步机制——信号量Semaphore。

01、信号量是什么?
在 C# 中,信号量(Semaphore)是一种用于线程同步的机制,能够控制对共享资源的访问。它的工作原理是通过维护一个计数器来控制对资源的访问次数。它常用于限制对共享资源(如数据库连接池、文件系统、网络资源等)的并发访问。
1、信号量有三个核心概念:
1.计数:信号量的核心是一个计数器,表示当前资源的可用数量;
2.等待:当线程请求资源时,此次如果计数器大于0,则线程可以继续执行,同时计数器减1;如果计数器等于0,则线程被阻塞直至其他线程释放资源,即有线程增加计数器的值;
3.释放:当线程使用完资源后,则需要释放信号量,同时计数器加1,并唤醒其他等待的线程;
相信理解了信号量核心概念,其工作原理就不言而喻。
2、应用场景:
通过对信号量的工作原理了解,我们可以总结为:信号量就是为了控制对共享资源的访问,保证共享资源不会被过度使用,因此可以引申出以下适用于信号量的场景:
1.控制各种连接池:限制同时打开各种资源的连接数量(比如连接打印机数量,数据库连接数据,文件访问数量);
2.限制网络请求:防止服务器过载,导致服务器崩溃;
3.协调多个线程的执行顺序:通过信号量控制生成者和消费者之间的资源访问, 实现生产者和消费者模型;
02、C#中的信号量实现
C#提供了两种信号量类型:Semaphore和SemaphoreSlim。其中两者功能基本相同,却又有所不同,而SemaphoreSlim是更轻量、更快速的信号量实现。下面是两种简单比较:
Semaphore: 是基于系统内核实现,属于内核级别同步,支持跨进程资源同步,因此性能较低,内存占用较大;它可以一次释放多个信号量,但是没有提供原生的异步支持;
SemaphoreSlim: 是用户级别同步,并不依赖系统内核,因此不支持跨进程资源同步,因此性能更高,内存占用更低;它一次只能释放一个信号量,但是提供了原生异步支持;

03、Semaphore使用示例
通过对信号量原理的详细了解,而作为对信号量实现类Semaphore,这些原理也同样适用,因此Semaphore类的构造函数就指定了用于控制线程数量的参数。其构造函数如下:
public Semaphore(int initialCount, int maximumCount);
public Semaphore(int initialCount, int maximumCount, string name);
initialCount: 初始化信号量的计数,表示初始时可以同时访问资源的线程数量。
maximumCount: 信号量的最大计数,表示允许同时访问资源的最大线程数。
name: 可选的名称,用于命名信号量对象(可在多个进程间共享信号量)。
然后可以用WaitOne方法获取信号量,使用Release方法释放信号量。
下面我们做一个小例子,创建一个初始化为2个线程的信号量Semaphore,然后启动5个线程用来访问信号量,并在获取信号量之前、之后以及释放信号量之前都加上日志,用来观察线程被控制的过程。代码如下:
public class SemaphoreExample
{
//初始化最多2个线程同时进入, 最大允许3个线程
private static Semaphore semaphore = new Semaphore(2, 3);
//用于不同的线程显示不同的颜色,方便观察结果
private static ConsoleColor[] colors = new ConsoleColor[5]
{
ConsoleColor.Red,
ConsoleColor.White,
ConsoleColor.Yellow,
ConsoleColor.Green,
ConsoleColor.Blue
};
public static void Worker(object? i)
{
var id = (int)i;
var color = colors[id];
PrintText.SafeForegroundColor($"线程 {id} 等待进入...", color);
//请求进入信号量(如果资源不可用,则返回)
semaphore.WaitOne();
PrintText.SafeForegroundColor($"线程 {id} 已 [ 进入 ] 同步代码块.", color);
//业务处理
Thread.Sleep(2000);
PrintText.SafeForegroundColor($"线程 {id} 已 [ 离开 ] 同步代码块.", color);
//释放信号量(让其他线程可以进入)
semaphore.Release();
}
}
public static void SemaphoreRun()
{
for (int i = 0; i < 5; i++)
{
Thread t = new Thread(SemaphoreExample.Worker);
t.Start(i);
}
}
我们一起看看执行结果:

可以发现在红框部分为所有线程初始化完成,同时只有两个线程获取到信号量,之后就是又一个信号量释放成功,则紧跟着一个等待线程会立马进入,直到所有线程处理完成。
到这里会有一个疑问,我们在初始化信号量Semaphore时,指定了最大允许3个线程可以同时进入,但是上面示例并没有体现出来,这是为什么呢?
这是因为在信号量的生命周期中,maximumCount参数并不会直接改变信号量的当前可用资源数量,而是限制并发线程数的最大值。因此如果要想看到效果,则需要经过特殊处理才行,比如我们在上面的代码中释放信号量时,我们执行两次释放操作,但是这样会导致最后释放操作报SemaphoreFullException异常,因此要注意获取和释放信号量操作要配对,这里仅仅为了演示,执行结果如下:

可以看到maximumCount最大访问线程数生效了。
04、Semaphore使用注意事项
1.确保每个调用 WaitOne方法 的线程最终都会调用 Release方法。否则,可能会导致死锁。
2.确保Release方法的调用次数不应超过 WaitOne方法的调用次数,否则会抛出 SemaphoreFullException异常
3.如果单进程程序尽量选择SemaphoreSlim,因为SemaphoreSlim性能更好。
4.如果需要跨进程同步可以使用带名称的构造函数 Semaphore(int initialCount, int maximumCount, string name)。
注:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner
并发编程 - 线程同步(九)之信号量Semaphore的更多相关文章
- Python并发编程-线程同步(线程安全)
Python并发编程-线程同步(线程安全) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 线程同步,线程间协调,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直 ...
- 33 - 并发编程-线程同步-Event-lock
目录 1 线程同步 1.1 Event 1.1.1 什么是Flag? 1.1.2 Event原理 1.1.3 吃包子 1.2 Lock 1.2.1 lock方法 1.2.2 计数器 1.2.3 非阻塞 ...
- C#并行编程-线程同步原语
菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 ...
- java并发编程笔记(九)——多线程并发最佳实践
java并发编程笔记(九)--多线程并发最佳实践 使用本地变量 使用不可变类 最小化锁的作用域范围 使用线程池Executor,而不是直接new Thread执行 宁可使用同步也不要使用线程的wait ...
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch ...
- 14、Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch ...
- 【转】Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
Java并发编程:CountDownLatch.CyclicBarrier和Semaphore Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在j ...
- Java并发编程:同步容器
Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...
- 【转】Java并发编程:同步容器
为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...
- 编写高质量代码改善C#程序的157个建议——建议72:在线程同步中使用信号量
建议72:在线程同步中使用信号量 所谓线程同步,就是多个线程在某个对象上执行等待(也可理解为锁定该对象),直到该对象被解除锁定.C#中对象的类型分为引用类型和值类型.CLR在这两种类型上的等待是不一样 ...
随机推荐
- 实现不可逆加密文件:探索 GodoOS 的安全机制
在当今数字化时代,数据安全成为了企业和个人关注的重点.为了保护敏感信息不被未授权访问,各种加密技术应运而生.本文将探讨 GodoOS 项目中实现的一种不可逆加密文件的方法,重点介绍其背后的原理和技术细 ...
- 使用AES加密时,结果不一样
使用AES加密时,发现得到的结果不一致. python示例 from Crypto.Cipher import AES from Crypto.Util.Padding import pad from ...
- 鸿蒙NEXT开发案例:九宫格随机
[引言] 在鸿蒙NEXT开发中,九宫格抽奖是一个常见且有趣的应用场景.通过九宫格抽奖,用户可以随机获得不同奖品,增加互动性和趣味性.本文将介绍如何使用鸿蒙开发框架实现九宫格抽奖功能,并通过代码解析展示 ...
- CVE-2023-32233 在 Google KCTF 中的漏洞利用方案分析
这是对前文的补充,增加一种漏洞利用方案的分析,前文地址: https://www.cnblogs.com/hac425/p/17967844/cve202332233-vulnerability-an ...
- 【分块】LibreOJ 6278 数列分块入门2
题目 https://loj.ac/p/6278 题解 将 \(n\) 个元素的数组 \(a\) 按块长 \(\sqrt{n}\) 进行分块处理.为每个块设置一个懒添加标记 \(add[i]\),代表 ...
- 爬虫自动化脚本+AI赋能
简介 估计大家对网页爬取和数据抓取已经有所了解,市面上也有许多现成的软件可供使用.例如,前几天群里有位朋友利用爬虫技术抓取了AV网站,并搭建了一个磁力链接站点. 本文将介绍如何模拟手动操作,将一些繁琐 ...
- Qt编写的modbus模拟器/支持网络和串口以及websocket/支持网络rtu
一.使用说明 1.1 设备模拟-Com 第一步,填写要模拟的设备地址,0表示自动处理,也就是收到什么地址就应答什么地址. 第二步,填写对应的串口号和波特率. 第三步,单击打开串口,成功后会变成关闭串口 ...
- Qt编写物联网管理平台43-告警短信转发
一.前言 系统在运行过程中,会实时采集设备的数据,当采集到的数据发生报警后,可以将报警信息以短信的形式发送给指定的管理员(可以是多个),这样管理员就算不在现场,也能第一时间知道哪里发生了报警,可以紧急 ...
- Qt编写安防视频监控系统66-子模块10网页浏览
一.前言 网页浏览模块,用于传入一个网页地址,打开对应的网页进行浏览,可用于网页展示信息,支持多个,可以自行增加,代码中演示了一个.此模块的用途属于添砖加瓦润色用的,比如有一个牛逼的3D网页,机器人. ...
- Eclipse中如何将web应用部署到tomcat
第1种方法: 将workspace下的复制到tomcat的webapp下(我看度还是算了,看着都觉得麻烦). 第2种方法:总体思路就是:(1)先为web应用创建Tomcat Server:(2)再将w ...