C# 多线程访问之 SemaphoreSlim(信号量)【进阶篇】
SemaphoreSlim 是对可同时访问某一共享资源或资源池的线程数加以限制的 Semaphore 的轻量替代,也可在等待时间预计很短的情况下用于在单个进程内等待。
由于 SemaphoreSlim 更加轻量、快速,因此推荐使用,本文也着重介绍。
一、简介
相较于线程锁的使一块代码只能一个线程访问,SemaphoreSlim 则是让同一块代码让多个线程同时访问,并且总数量可控。
SemaphoreSlim 尽可能多地依赖公共语言运行时 (CLR) 提供的同步基元。 还提供延迟初始化、基于内核的等待句柄。
SemaphoreSlim 也支持使用取消标记,但不支持命名信号量或使用用于同步的等待句柄。
线程通过调用从 WaitHandle 类中继承的 WaitOne 方法进入信号量,无论对于 System.Threading.Semaphore 对象、SemaphoreSlim.Wait 或 SemaphoreSlim.WaitAsync 方法还是 SemaphoreSlim 对象都适用。
当调用返回时,信号量计数会减少,当线程请求进入且计数为零时,此线程受到阻止。 线程通过调用 Semaphore.Release 或 SemaphoreSlim.Release 方法释放信号量时,允许受阻线程进入,此时信号量计数会增加。
受阻线程进入信号量无保证的顺序,比如先进先出 (FIFO) 或按后进先出 (LIFO)。
二、用法示例
关于 SemaphoreSlim、Wait()、Release() 的一个示例。
特别注意:若初始信号量为 0,则需要手动释放(Release())信号量。
class Program
{
private static SemaphoreSlim semaphore;
private static int padding; // 增加固定时间间隔,使输出更有序
static void Main(string[] args)
{
// 创建 semaphore 对象
semaphore = new SemaphoreSlim(0, 4); // (初始数量,最大数量)
Console.WriteLine($"semaphore 中现有 {semaphore.CurrentCount} 个信号量");
Task[] tasks = new Task[10];
for (int i = 0; i <= 9; i++)
{
tasks[i] = Task.Run(() =>
{
Console.WriteLine($"任务 {Task.CurrentId} 准备进入 semaphore");
int semaphoreCount;
semaphore.Wait(); // 调用 Wait() 方法,标记等待进入信号量
try
{
Interlocked.Add(ref padding, 100);
Console.WriteLine($"任务 {Task.CurrentId} 进入 semaphore");
Thread.Sleep(1000 + padding);
}
finally
{
semaphoreCount = semaphore.Release(); // 调用 Release() 方法,释放信号量
// semaphoreCount:释放当前信号量之前的信号量
}
Console.WriteLine($"任务 {Task.CurrentId} 完成,释放 semaphore 一个信号量;释放前信号量:{semaphoreCount}");
});
}
Thread.Sleep(500); // 暂时阻塞主线程,让全部线程到位
Console.Write("主线程调用 Release(4) 释放四个信号量--> ");
semaphore.Release(4); // 由于初始数量为 0 所以需要手动释放信号量
Console.WriteLine($"semaphore 现有信号量 {semaphore.CurrentCount}");
Task.WaitAll(tasks); // 等待全部线程完成
Console.WriteLine("主线程退出");
}
}
输出结果:

三、属性 or 函数 or 方法释义
属性-AvailableWaitHandle
返回一个 WaitHandle 对象,即封装等待对共享资源的独占访问的操作系统对象。
属性-CurrentCount
指的是对于 SemaphoreSlim 对象,可以输入信号量的剩余线程数。
属性的初始值 CurrentCount 由对类构造函数的 SemaphoreSlim 调用设置。 每次对 Wait 或 WaitAsync 方法的调用会递减,并按对 Release 方法的每此调用递增。
构造方法-SemaphoreSlim(Int32)
public SemaphoreSlim (int initialCount);
初始化 SemaphoreSlim 类的新实例,以指定可同时授予的请求的初始数量,但未指定最大请求数。
若初始数量为 0,则需要手动释放指定数量的信号量;若小于 0,则抛出异常:ArgumentOutOfRangeException。
构造方法-SemaphoreSlim(Int32, Int32)
public SemaphoreSlim (int initialCount, int maxCount);
初始化 SemaphoreSlim 类的新实例,同时指定可同时授予的请求的初始数量和最大数量。
若initialCount 小于 0,或 initialCount 大于 maxCount,或 maxCount 小于等于 0,则抛出异常:ArgumentOutOfRangeException。
方法-Dispose
用于释放由 SemaphoreSlim 类的当前实例使用的所有资源。
SemaphoreSlim 与大多数成员不同,Dispose 不是线程安全的,不能与此实例的其他成员同时使用。
// 若要释放托管资源和非托管资源,则为 true(缺省默认);
// 若仅释放非托管资源,则为 false
protected virtual void Dispose (bool disposing);
方法-Release
对 Release() 方法的调用将属性递增一个 CurrentCount 数。 如果在调用此方法之前 CurrentCount 属性值为零,该方法还允许调用 Wait 或 WaitAsync 方法,阻止一个线程或任务进入信号灯。
// 释放 SemaphoreSlim 对象指定的次数
public int Release (int releaseCount);
方法-Wait
阻止当前线程,直至它可进入 SemaphoreSlim 对象为止。
// 使用 TimeSpan 来指定超时,同时监视取消动作 CancellationToken
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
public bool Wait (TimeSpan timeout, System.Threading.CancellationToken cancellationToken);
// TimeSpan 表示要等待的毫秒数,-1 代表无限等待,0 代表立即返回-测试用例
// 阻止当前线程,直至它可进入 SemaphoreSlim 为止
// 并使用 32 位带符号整数来指定超时(-1 代表无限等待),同时监视取消操作 CancellationToken
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
public bool Wait (int millisecondsTimeout, System.Threading.CancellationToken cancellationToken);
// 返回值 bool:如果当前线程成功进入 SemaphoreSlim,则为 true;否则为 false
// 阻止当前线程,直至它可进入 SemaphoreSlim 为止
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
public void Wait ();
方法-WaitAsync
此为 Wait 方法的异步方式,优势在于对线程不会独占,即不会独占当前线程直到释放信号量。
若将本文第二部分中的代码:(两处修改)
semaphore.Wait();
// 1/2 改为以下,异步方式
semaphore.WaitAsync();
// 2/2 并记录线程 ID(在如下位置添加一行打印信息)
try
{
Console.WriteLine($"任务 {Task.CurrentId} 进入 semaphore");
Console.WriteLine($"ProcessorId {Thread.GetCurrentProcessorId()}");// 新增行
Interlocked.Add(ref padding, 100);
Thread.Sleep(1000 + padding);
}
输出的结果:(可见打印出来的线程 ID 有相同的情况,说明并非独占)

参考官方:SemaphoreSlim 类
注:个人记录,有问题欢迎指正。
C# 多线程访问之 SemaphoreSlim(信号量)【进阶篇】的更多相关文章
- C# 多线程访问之 SemaphoreSlim(信号量)【C# 进阶】
SemaphoreSlim 是对可同时访问某一共享资源或资源池的线程数加以限制的 Semaphore 的轻量替代,也可在等待时间预计很短的情况下用于在单个进程内等待. 由于 SemaphoreSlim ...
- Java多线程编程实战指南(核心篇)读书笔记(三)
(尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76686044冷血之心的博客) 博主准备恶补一番Java高并发编程相 ...
- 多线程合集(一)---信号量,锁,以及并发编程,自定义任务调度和awaiter
引言 在后端开发中,多线程技术总是后端开发中常用到的技术,那什么是多线程呢,在操作系统中,程序运行的最小单位是进程,那线程则是进程里面的最小单位,关系是一对多的关系,而线程的调度,是由操作系统的时间片 ...
- Membership三步曲之进阶篇 - 深入剖析Provider Model
Membership 三步曲之进阶篇 - 深入剖析Provider Model 本文的目标是让每一个人都知道Provider Model 是什么,并且能灵活的在自己的项目中使用它. Membershi ...
- python 面向对象(进阶篇)
上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使 ...
- SQL Server调优系列进阶篇(查询语句运行几个指标值监测)
前言 上一篇我们分析了查询优化器的工作方式,其中包括:查询优化器的详细运行步骤.筛选条件分析.索引项优化等信息. 本篇我们分析在我们运行的过程中几个关键指标值的检测. 通过这些指标值来分析语句的运行问 ...
- SQL Server调优系列进阶篇(如何维护数据库索引)
前言 上一篇我们研究了如何利用索引在数据库里面调优,简要的介绍了索引的原理,更重要的分析了如何选择索引以及索引的利弊项,有兴趣的可以点击查看. 本篇延续上一篇的内容,继续分析索引这块,侧重索引项的日常 ...
- 【C#】【Thread】Semaphore/SemaphoreSlim信号量
System.Threading.Semaphore 类表示一个命名(系统范围)信号量或本地信号量. 它是一个对 Win32 信号量对象的精简包装. Win32 信号量是计数信号量,可用于控制对资源池 ...
- Python之路【第十七篇】:Django【进阶篇 】
Python之路[第十七篇]:Django[进阶篇 ] Model 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用 MySQLdb 来连接 ...
- PHP学习笔记 - 进阶篇(7)
PHP学习笔记 - 进阶篇(7) 文件操作 读取文件内容 PHP具有丰富的文件操作函数,最简单的读取文件的函数为file_get_contents,可以将整个文件全部读取到一个字符串中. $conte ...
随机推荐
- vCenter报错:Log Disk Exhaustion on 10
vCenter报错:Log Disk Exhaustion on 10 1.问题现象: 巡检时发现 vCenter Server 中,错误显示为:Log Disk Exhaustion on 10(字 ...
- 【漏洞分析】ReflectionToken BEVO代币攻击事件分析
前言 BEVO代币是一种Reflection Token(反射型代币),并且拥有通缩的特性.关于Reflection Token更为详细的说明可参考这篇文章.然后目前浏览到的很多分析报告没有指出其漏洞 ...
- 带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性 1.什么是语言模型? 大家或多或少都听过 ChatGPT 是一个 LLMs,那 LLMs 是什么? ...
- 2022-11-11:设计一个最大栈数据结构,既支持栈操作,又支持查找栈中最大元素。 实现 MaxStack 类: MaxStack() 初始化栈对象 void push(int x) 将元素 x 压
2022-11-11:设计一个最大栈数据结构,既支持栈操作,又支持查找栈中最大元素. 实现 MaxStack 类: MaxStack() 初始化栈对象 void push(int x) 将元素 x 压 ...
- 2020-11-06:go中,谈一下调度器。
福哥答案2020-11-06:·MPG模型:goroutine的并发模型可以归纳为MPG模型:·MPG概念:线程(machine,系统线程,物理线程)-内核(processor)-协程(gorouti ...
- 2021-03-05:go中,io密集型的应用,比如有很多文件io,磁盘io,网络io,调大GOMAXPROCS,会不会对性能有帮助?为什么?
2021-03-05:go中,io密集型的应用,比如有很多文件io,磁盘io,网络io,调大GOMAXPROCS,会不会对性能有帮助?为什么? 福哥答案2021-03-05: 这是面试中被问到的.实力 ...
- 2021-06-01:K个逆序对数组。给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个逆序对的不同的数组的个数。逆序对的定义如下:对于数组的第i个和第 j个元素,如果满
2021-06-01:K个逆序对数组.给出两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个逆序对的不同的数组的个数.逆序对的定义如下:对于数组的第i个和第 j个元素,如果满 ...
- vue全家桶进阶之路24:Mock
Mock 是一个 JavaScript 库,用于生成随机数据或模拟 HTTP 请求响应,用于前端开发中的单元测试.功能测试.集成测试等场景. Mock 可以生成各种类型的数据,包括字符串.数字.布尔值 ...
- drf——全局处理异常、接口文档、jwt介绍、based64编码与解码
全局异常处理原理 # 对于前端来讲,后端即便报错,也要返回统一的格式,前端便于处理 {code:999,msg:'系统异常,请联系系统管理员'} # 只要三大认证,视图类的方法出了异常,都会执行一个函 ...
- nas盒子内网穿透
2023年5月27日星期六 -------------------------------------------------------------------------------------- ...